aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Bennett <hello@vantezzen.io>2020-03-25 10:30:08 +0100
committerLibravatar GitHub <noreply@github.com>2020-03-25 10:30:08 +0100
commit1cc9c70e4774f4da8b1ad927d6cb7f74c0ce730a (patch)
treec66660ee310029431c6943b31d6f8c53fcc59616 /src
parentSupport new recipe repository hierarchy (#492) (diff)
downloadferdium-app-1cc9c70e4774f4da8b1ad927d6cb7f74c0ce730a.tar.gz
ferdium-app-1cc9c70e4774f4da8b1ad927d6cb7f74c0ce730a.tar.zst
ferdium-app-1cc9c70e4774f4da8b1ad927d6cb7f74c0ce730a.zip
Improve user onboarding (#493)
Until now, new users have started on the Ferdi dashboard and not on the Welcome screen like Franz does it. This change was made, as users needed to be ablet to change their server before logging in. This commit will change this onboarding process to bring users to the welcome screen on first login and it adds a new "Change Server" screen during authentication that allows the user to change the server without going to the full settings screen. This way, the onboarding experience for new users is a lot easier to go though while also improving the experience for experienced users who want to change their server as they only get the option they are looking for and not the whole settings list.
Diffstat (limited to 'src')
-rw-r--r--src/app.js2
-rw-r--r--src/components/auth/ChangeServer.js80
-rw-r--r--src/components/auth/Login.js4
-rw-r--r--src/components/auth/Signup.js7
-rw-r--r--src/components/auth/Welcome.js4
-rw-r--r--src/containers/auth/ChangeServerScreen.js50
-rw-r--r--src/containers/auth/LoginScreen.js1
-rw-r--r--src/containers/auth/SignupScreen.js1
-rw-r--r--src/containers/auth/WelcomeScreen.js1
-rw-r--r--src/i18n/locales/defaultMessages.json44
-rw-r--r--src/i18n/locales/en-US.json3
-rw-r--r--src/i18n/messages/src/components/auth/ChangeServer.json41
m---------src/internal-server0
-rw-r--r--src/stores/UserStore.js8
14 files changed, 241 insertions, 5 deletions
diff --git a/src/app.js b/src/app.js
index d8fa37014..0e24420c3 100644
--- a/src/app.js
+++ b/src/app.js
@@ -35,6 +35,7 @@ import WelcomeScreen from './containers/auth/WelcomeScreen';
35import LoginScreen from './containers/auth/LoginScreen'; 35import LoginScreen from './containers/auth/LoginScreen';
36import LockedScreen from './containers/auth/LockedScreen'; 36import LockedScreen from './containers/auth/LockedScreen';
37import PasswordScreen from './containers/auth/PasswordScreen'; 37import PasswordScreen from './containers/auth/PasswordScreen';
38import ChangeServerScreen from './containers/auth/ChangeServerScreen';
38import SignupScreen from './containers/auth/SignupScreen'; 39import SignupScreen from './containers/auth/SignupScreen';
39import ImportScreen from './containers/auth/ImportScreen'; 40import ImportScreen from './containers/auth/ImportScreen';
40import PricingScreen from './containers/auth/PricingScreen'; 41import PricingScreen from './containers/auth/PricingScreen';
@@ -97,6 +98,7 @@ window.addEventListener('load', () => {
97 <Route path="/auth/welcome" component={WelcomeScreen} /> 98 <Route path="/auth/welcome" component={WelcomeScreen} />
98 <Route path="/auth/login" component={LoginScreen} /> 99 <Route path="/auth/login" component={LoginScreen} />
99 <Route path="/auth/locked" component={LockedScreen} /> 100 <Route path="/auth/locked" component={LockedScreen} />
101 <Route path="/auth/server" component={ChangeServerScreen} />
100 <Route path="/auth/signup"> 102 <Route path="/auth/signup">
101 <IndexRedirect to="/auth/signup/form" /> 103 <IndexRedirect to="/auth/signup/form" />
102 <Route path="/auth/signup/form" component={SignupScreen} /> 104 <Route path="/auth/signup/form" component={SignupScreen} />
diff --git a/src/components/auth/ChangeServer.js b/src/components/auth/ChangeServer.js
new file mode 100644
index 000000000..433334b6c
--- /dev/null
+++ b/src/components/auth/ChangeServer.js
@@ -0,0 +1,80 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import Input from '../ui/Input';
8import Button from '../ui/Button';
9
10const messages = defineMessages({
11 headline: {
12 id: 'changeserver.headline',
13 defaultMessage: '!!!Change server',
14 },
15 label: {
16 id: 'changeserver.label',
17 defaultMessage: '!!!Server',
18 },
19 submit: {
20 id: 'changeserver.submit',
21 defaultMessage: '!!!Submit',
22 },
23});
24
25export default @observer class ChangeServer extends Component {
26 static propTypes = {
27 onSubmit: PropTypes.func.isRequired,
28 server: PropTypes.string.isRequired,
29 };
30
31 static contextTypes = {
32 intl: intlShape,
33 };
34
35 form = new Form({
36 fields: {
37 server: {
38 label: this.context.intl.formatMessage(messages.label),
39 value: '',
40 },
41 },
42 }, this.context.intl);
43
44 componentDidMount() {
45 this.form.$('server').value = this.props.server;
46 }
47
48 submit(e) {
49 e.preventDefault();
50 this.form.submit({
51 onSuccess: (form) => {
52 this.props.onSubmit(form.values());
53 },
54 onError: () => { },
55 });
56 }
57
58 render() {
59 const { form } = this;
60 const { intl } = this.context;
61
62 return (
63 <div className="auth__container">
64 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
65 <h1>{intl.formatMessage(messages.headline)}</h1>
66
67 <Input
68 field={form.$('server')}
69 focus
70 />
71 <Button
72 type="submit"
73 className="auth__button"
74 label={intl.formatMessage(messages.submit)}
75 />
76 </form>
77 </div>
78 );
79 }
80}
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
index e25121de0..f33d134c8 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -78,6 +78,7 @@ export default @inject('actions') @observer class Login extends Component {
78 isServerLogout: PropTypes.bool.isRequired, 78 isServerLogout: PropTypes.bool.isRequired,
79 signupRoute: PropTypes.string.isRequired, 79 signupRoute: PropTypes.string.isRequired,
80 passwordRoute: PropTypes.string.isRequired, 80 passwordRoute: PropTypes.string.isRequired,
81 changeServerRoute: PropTypes.string.isRequired,
81 error: globalErrorPropType.isRequired, 82 error: globalErrorPropType.isRequired,
82 actions: PropTypes.object.isRequired, 83 actions: PropTypes.object.isRequired,
83 }; 84 };
@@ -127,6 +128,7 @@ export default @inject('actions') @observer class Login extends Component {
127 isServerLogout, 128 isServerLogout,
128 signupRoute, 129 signupRoute,
129 passwordRoute, 130 passwordRoute,
131 changeServerRoute,
130 error, 132 error,
131 } = this.props; 133 } = this.props;
132 134
@@ -194,7 +196,7 @@ export default @inject('actions') @observer class Login extends Component {
194 )} 196 )}
195 </form> 197 </form>
196 <div className="auth__links"> 198 <div className="auth__links">
197 <Link to="/settings/app">{intl.formatMessage(messages.changeServer)}</Link> 199 <Link to={changeServerRoute}>{intl.formatMessage(messages.changeServer)}</Link>
198 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> 200 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a>
199 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> 201 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
200 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link> 202 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link>
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index a166155a7..6a7db5cde 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -79,6 +79,7 @@ export default @inject('actions') @observer class Signup extends Component {
79 onSubmit: PropTypes.func.isRequired, 79 onSubmit: PropTypes.func.isRequired,
80 isSubmitting: PropTypes.bool.isRequired, 80 isSubmitting: PropTypes.bool.isRequired,
81 loginRoute: PropTypes.string.isRequired, 81 loginRoute: PropTypes.string.isRequired,
82 changeServerRoute: PropTypes.string.isRequired,
82 error: globalErrorPropType.isRequired, 83 error: globalErrorPropType.isRequired,
83 actions: PropTypes.object.isRequired, 84 actions: PropTypes.object.isRequired,
84 }; 85 };
@@ -130,7 +131,9 @@ export default @inject('actions') @observer class Signup extends Component {
130 render() { 131 render() {
131 const { form } = this; 132 const { form } = this;
132 const { intl } = this.context; 133 const { intl } = this.context;
133 const { isSubmitting, loginRoute, error } = this.props; 134 const {
135 isSubmitting, loginRoute, error, changeServerRoute,
136 } = this.props;
134 137
135 const termsBase = window.ferdi.stores.settings.all.app.server !== 'https://api.franzinfra.com' ? window.ferdi.stores.settings.all.app.server : 'https://meetfranz.com'; 138 const termsBase = window.ferdi.stores.settings.all.app.server !== 'https://api.franzinfra.com' ? window.ferdi.stores.settings.all.app.server : 'https://meetfranz.com';
136 139
@@ -198,7 +201,7 @@ export default @inject('actions') @observer class Signup extends Component {
198 </p> 201 </p>
199 </form> 202 </form>
200 <div className="auth__links"> 203 <div className="auth__links">
201 <Link to="/settings/app">{intl.formatMessage(messages.changeServer)}</Link> 204 <Link to={changeServerRoute}>{intl.formatMessage(messages.changeServer)}</Link>
202 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> 205 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a>
203 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> 206 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
204 </div> 207 </div>
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js
index 1453c1d7c..6e742e0c1 100644
--- a/src/components/auth/Welcome.js
+++ b/src/components/auth/Welcome.js
@@ -26,6 +26,7 @@ export default @inject('actions') @observer class Login extends Component {
26 static propTypes = { 26 static propTypes = {
27 loginRoute: PropTypes.string.isRequired, 27 loginRoute: PropTypes.string.isRequired,
28 signupRoute: PropTypes.string.isRequired, 28 signupRoute: PropTypes.string.isRequired,
29 changeServerRoute: PropTypes.string.isRequired,
29 recipes: MobxPropTypes.arrayOrObservableArray.isRequired, 30 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
30 actions: PropTypes.object.isRequired, 31 actions: PropTypes.object.isRequired,
31 }; 32 };
@@ -43,6 +44,7 @@ export default @inject('actions') @observer class Login extends Component {
43 const { 44 const {
44 loginRoute, 45 loginRoute,
45 signupRoute, 46 signupRoute,
47 changeServerRoute,
46 recipes, 48 recipes,
47 } = this.props; 49 } = this.props;
48 50
@@ -71,7 +73,7 @@ export default @inject('actions') @observer class Login extends Component {
71 <br /> 73 <br />
72 74
73 75
74 <Link to="settings/app"> 76 <Link to={changeServerRoute}>
75 <span style={{ 77 <span style={{
76 textAlign: 'center', 78 textAlign: 'center',
77 width: '100%', 79 width: '100%',
diff --git a/src/containers/auth/ChangeServerScreen.js b/src/containers/auth/ChangeServerScreen.js
new file mode 100644
index 000000000..5c58087a3
--- /dev/null
+++ b/src/containers/auth/ChangeServerScreen.js
@@ -0,0 +1,50 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5import ChangeServer from '../../components/auth/ChangeServer';
6import SettingsStore from '../../stores/SettingsStore';
7
8export default @inject('stores', 'actions') @observer class ChangeServerScreen extends Component {
9 constructor(props) {
10 super(props);
11
12 this.onSubmit = this.onSubmit.bind(this);
13 }
14
15 onSubmit(values) {
16 const { server } = values;
17
18 this.props.actions.settings.update({
19 type: 'app',
20 data: {
21 server,
22 },
23 });
24 this.props.stores.router.push('/auth');
25 }
26
27 render() {
28 const { stores } = this.props;
29 const { server } = stores.settings.all.app;
30
31 return (
32 <ChangeServer
33 onSubmit={this.onSubmit}
34 server={server}
35 />
36 );
37 }
38}
39
40ChangeServerScreen.wrappedComponent.propTypes = {
41 actions: PropTypes.shape({
42 settings: PropTypes.shape({
43 update: PropTypes.func.isRequired,
44 }).isRequired,
45 }).isRequired,
46 stores: PropTypes.shape({
47 settings: PropTypes.instanceOf(SettingsStore).isRequired,
48 router: PropTypes.instanceOf(RouterStore).isRequired,
49 }).isRequired,
50};
diff --git a/src/containers/auth/LoginScreen.js b/src/containers/auth/LoginScreen.js
index e5ee10785..d17820ad6 100644
--- a/src/containers/auth/LoginScreen.js
+++ b/src/containers/auth/LoginScreen.js
@@ -21,6 +21,7 @@ export default @inject('stores', 'actions') @observer class LoginScreen extends
21 isServerLogout={stores.user.logoutReason === stores.user.logoutReasonTypes.SERVER} 21 isServerLogout={stores.user.logoutReason === stores.user.logoutReasonTypes.SERVER}
22 signupRoute={stores.user.signupRoute} 22 signupRoute={stores.user.signupRoute}
23 passwordRoute={stores.user.passwordRoute} 23 passwordRoute={stores.user.passwordRoute}
24 changeServerRoute={stores.user.changeServerRoute}
24 error={error} 25 error={error}
25 /> 26 />
26 ); 27 );
diff --git a/src/containers/auth/SignupScreen.js b/src/containers/auth/SignupScreen.js
index f93498be2..803fe1cd9 100644
--- a/src/containers/auth/SignupScreen.js
+++ b/src/containers/auth/SignupScreen.js
@@ -36,6 +36,7 @@ export default @inject('stores', 'actions') @observer class SignupScreen extends
36 onSubmit={values => this.onSignup(values)} 36 onSubmit={values => this.onSignup(values)}
37 isSubmitting={stores.user.signupRequest.isExecuting} 37 isSubmitting={stores.user.signupRequest.isExecuting}
38 loginRoute={stores.user.loginRoute} 38 loginRoute={stores.user.loginRoute}
39 changeServerRoute={stores.user.changeServerRoute}
39 error={error} 40 error={error}
40 /> 41 />
41 ); 42 );
diff --git a/src/containers/auth/WelcomeScreen.js b/src/containers/auth/WelcomeScreen.js
index 75182345a..6f2d0eee6 100644
--- a/src/containers/auth/WelcomeScreen.js
+++ b/src/containers/auth/WelcomeScreen.js
@@ -14,6 +14,7 @@ export default @inject('stores', 'actions') @observer class LoginScreen extends
14 <Welcome 14 <Welcome
15 loginRoute={user.loginRoute} 15 loginRoute={user.loginRoute}
16 signupRoute={user.signupRoute} 16 signupRoute={user.signupRoute}
17 changeServerRoute={user.changeServerRoute}
17 recipes={recipePreviews.featured} 18 recipes={recipePreviews.featured}
18 /> 19 />
19 ); 20 );
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index be0cd008b..f825d773b 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -46,6 +46,50 @@
46 { 46 {
47 "descriptors": [ 47 "descriptors": [
48 { 48 {
49 "defaultMessage": "!!!Change server",
50 "end": {
51 "column": 3,
52 "line": 14
53 },
54 "file": "src/components/auth/ChangeServer.js",
55 "id": "changeserver.headline",
56 "start": {
57 "column": 12,
58 "line": 11
59 }
60 },
61 {
62 "defaultMessage": "!!!Server",
63 "end": {
64 "column": 3,
65 "line": 18
66 },
67 "file": "src/components/auth/ChangeServer.js",
68 "id": "changeserver.label",
69 "start": {
70 "column": 9,
71 "line": 15
72 }
73 },
74 {
75 "defaultMessage": "!!!Submit",
76 "end": {
77 "column": 3,
78 "line": 22
79 },
80 "file": "src/components/auth/ChangeServer.js",
81 "id": "changeserver.submit",
82 "start": {
83 "column": 10,
84 "line": 19
85 }
86 }
87 ],
88 "path": "src/components/auth/ChangeServer.json"
89 },
90 {
91 "descriptors": [
92 {
49 "defaultMessage": "!!!Import your Ferdi 4 services", 93 "defaultMessage": "!!!Import your Ferdi 4 services",
50 "end": { 94 "end": {
51 "column": 3, 95 "column": 3,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 4c14c8f90..d9613dc43 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -1,6 +1,9 @@
1{ 1{
2 "app.errorHandler.action": "Reload", 2 "app.errorHandler.action": "Reload",
3 "app.errorHandler.headline": "Something went wrong", 3 "app.errorHandler.headline": "Something went wrong",
4 "changeserver.headline": "Change server",
5 "changeserver.label": "Server",
6 "changeserver.submit": "Submit",
4 "feature.announcements.changelog.headline": "Changes in Ferdi {version}", 7 "feature.announcements.changelog.headline": "Changes in Ferdi {version}",
5 "feature.debugger.title": "Publish debugging information", 8 "feature.debugger.title": "Publish debugging information",
6 "feature.delayApp.headline": "Please purchase a Ferdi Supporter License to skip waiting", 9 "feature.delayApp.headline": "Please purchase a Ferdi Supporter License to skip waiting",
diff --git a/src/i18n/messages/src/components/auth/ChangeServer.json b/src/i18n/messages/src/components/auth/ChangeServer.json
new file mode 100644
index 000000000..3a122d50c
--- /dev/null
+++ b/src/i18n/messages/src/components/auth/ChangeServer.json
@@ -0,0 +1,41 @@
1[
2 {
3 "id": "changeserver.headline",
4 "defaultMessage": "!!!Change server",
5 "file": "src/components/auth/ChangeServer.js",
6 "start": {
7 "line": 11,
8 "column": 12
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "changeserver.label",
17 "defaultMessage": "!!!Server",
18 "file": "src/components/auth/ChangeServer.js",
19 "start": {
20 "line": 15,
21 "column": 9
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 },
28 {
29 "id": "changeserver.submit",
30 "defaultMessage": "!!!Submit",
31 "file": "src/components/auth/ChangeServer.js",
32 "start": {
33 "line": 19,
34 "column": 10
35 },
36 "end": {
37 "line": 22,
38 "column": 3
39 }
40 }
41] \ No newline at end of file
diff --git a/src/internal-server b/src/internal-server
Subproject 3ad266e1371075612ce866332472595bfd89a5d Subproject 38fc9925d88971cee26cc08343da2f0e153c053
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index ec0b0cf8d..3a53d150d 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -34,6 +34,8 @@ export default class UserStore extends Store {
34 34
35 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`; 35 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
36 36
37 CHANGE_SERVER_ROUTE = `${this.BASE_ROUTE}/server`;
38
37 @observable loginRequest = new Request(this.api.user, 'login'); 39 @observable loginRequest = new Request(this.api.user, 'login');
38 40
39 @observable signupRequest = new Request(this.api.user, 'signup'); 41 @observable signupRequest = new Request(this.api.user, 'signup');
@@ -97,7 +99,7 @@ export default class UserStore extends Store {
97 99
98 // Reactions 100 // Reactions
99 this.registerReactions([ 101 this.registerReactions([
100 // this._requireAuthenticatedUser, 102 this._requireAuthenticatedUser,
101 this._getUserData.bind(this), 103 this._getUserData.bind(this),
102 this._resetTrialActivationState.bind(this), 104 this._resetTrialActivationState.bind(this),
103 ]); 105 ]);
@@ -137,6 +139,10 @@ export default class UserStore extends Store {
137 return this.PASSWORD_ROUTE; 139 return this.PASSWORD_ROUTE;
138 } 140 }
139 141
142 get changeServerRoute() {
143 return this.CHANGE_SERVER_ROUTE;
144 }
145
140 // Data 146 // Data
141 @computed get isLoggedIn() { 147 @computed get isLoggedIn() {
142 return Boolean(localStorage.getItem('authToken')); 148 return Boolean(localStorage.getItem('authToken'));