aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AppUpdateInfoBar.js2
-rw-r--r--src/components/auth/AuthLayout.js4
-rw-r--r--src/components/auth/Import.js4
-rw-r--r--src/components/auth/Locked.js115
-rw-r--r--src/components/auth/Login.js26
-rw-r--r--src/components/auth/Pricing.js2
-rw-r--r--src/components/auth/Signup.js9
-rw-r--r--src/components/auth/Welcome.js15
-rw-r--r--src/components/layout/AppLayout.js26
-rw-r--r--src/components/layout/Sidebar.js145
-rw-r--r--src/components/services/content/ServiceView.js77
-rw-r--r--src/components/services/content/ServiceWebview.js20
-rw-r--r--src/components/services/content/Services.js23
-rw-r--r--src/components/settings/account/AccountDashboard.js2
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js15
-rw-r--r--src/components/settings/services/EditServiceForm.js8
-rw-r--r--src/components/settings/settings/EditSettingsForm.js227
-rw-r--r--src/components/settings/supportFerdi/SupportFerdiDashboard.js73
-rw-r--r--src/components/settings/team/TeamDashboard.js4
-rw-r--r--src/components/ui/ActivateTrialButton/index.js19
-rw-r--r--src/components/ui/AppLoader/index.js17
-rw-r--r--src/components/ui/Button.js2
-rw-r--r--src/components/ui/FullscreenLoader/styles.js1
-rw-r--r--src/components/ui/Input.js2
-rw-r--r--src/components/ui/Link.js4
-rw-r--r--src/components/ui/Loader.js2
-rw-r--r--src/components/ui/Modal/index.js3
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js8
-rw-r--r--src/components/ui/UpgradeButton/index.js7
29 files changed, 715 insertions, 147 deletions
diff --git a/src/components/AppUpdateInfoBar.js b/src/components/AppUpdateInfoBar.js
index 4fb3a8b71..4108fdf12 100644
--- a/src/components/AppUpdateInfoBar.js
+++ b/src/components/AppUpdateInfoBar.js
@@ -8,7 +8,7 @@ import InfoBar from './ui/InfoBar';
8const messages = defineMessages({ 8const messages = defineMessages({
9 updateAvailable: { 9 updateAvailable: {
10 id: 'infobar.updateAvailable', 10 id: 'infobar.updateAvailable',
11 defaultMessage: '!!!A new update for Franz is available.', 11 defaultMessage: '!!!A new update for Ferdi is available.',
12 }, 12 },
13 changelog: { 13 changelog: {
14 id: 'infobar.buttonChangelog', 14 id: 'infobar.buttonChangelog',
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
index 75a8cfc61..0c5198583 100644
--- a/src/components/auth/AuthLayout.js
+++ b/src/components/auth/AuthLayout.js
@@ -52,7 +52,7 @@ export default @observer class AuthLayout extends Component {
52 52
53 return ( 53 return (
54 <> 54 <>
55 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 55 {isWindows && !isFullScreen && <TitleBar menu={window.ferdi.menu.template} icon="assets/images/logo.svg" />}
56 <div className="auth"> 56 <div className="auth">
57 {!isOnline && ( 57 {!isOnline && (
58 <InfoBar 58 <InfoBar
@@ -87,7 +87,7 @@ export default @observer class AuthLayout extends Component {
87 })} 87 })}
88 </div> 88 </div>
89 {/* </div> */} 89 {/* </div> */}
90 <Link to="https://adlk.io" className="auth__adlk" target="_blank"> 90 <Link to="https://github.com/getferdi/ferdi" className="auth__adlk" target="_blank">
91 <img src="./assets/images/adlk.svg" alt="" /> 91 <img src="./assets/images/adlk.svg" alt="" />
92 </Link> 92 </Link>
93 </div> 93 </div>
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js
index 0d5feb274..3e34c3162 100644
--- a/src/components/auth/Import.js
+++ b/src/components/auth/Import.js
@@ -12,11 +12,11 @@ import Button from '../ui/Button';
12const messages = defineMessages({ 12const messages = defineMessages({
13 headline: { 13 headline: {
14 id: 'import.headline', 14 id: 'import.headline',
15 defaultMessage: '!!!Import your Franz 4 services', 15 defaultMessage: '!!!Import your Ferdi 4 services',
16 }, 16 },
17 notSupportedHeadline: { 17 notSupportedHeadline: {
18 id: 'import.notSupportedHeadline', 18 id: 'import.notSupportedHeadline',
19 defaultMessage: '!!!Services not yet supported in Franz 5', 19 defaultMessage: '!!!Services not yet supported in Ferdi 5',
20 }, 20 },
21 submitButtonLabel: { 21 submitButtonLabel: {
22 id: 'import.submit.label', 22 id: 'import.submit.label',
diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.js
new file mode 100644
index 000000000..045621d0a
--- /dev/null
+++ b/src/components/auth/Locked.js
@@ -0,0 +1,115 @@
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 { required } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Button from '../ui/Button';
10import Infobox from '../ui/Infobox';
11
12import { globalError as globalErrorPropType } from '../../prop-types';
13
14const messages = defineMessages({
15 headline: {
16 id: 'locked.headline',
17 defaultMessage: '!!!Locked',
18 },
19 info: {
20 id: 'locked.info',
21 defaultMessage: '!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.',
22 },
23 passwordLabel: {
24 id: 'locked.password.label',
25 defaultMessage: '!!!Password',
26 },
27 submitButtonLabel: {
28 id: 'locked.submit.label',
29 defaultMessage: '!!!Unlock',
30 },
31 invalidCredentials: {
32 id: 'locked.invalidCredentials',
33 defaultMessage: '!!!Password invalid',
34 },
35});
36
37export default @observer class Locked extends Component {
38 static propTypes = {
39 onSubmit: PropTypes.func.isRequired,
40 isSubmitting: PropTypes.bool.isRequired,
41 error: globalErrorPropType.isRequired,
42 };
43
44 static contextTypes = {
45 intl: intlShape,
46 };
47
48 form = new Form({
49 fields: {
50 password: {
51 label: this.context.intl.formatMessage(messages.passwordLabel),
52 value: '',
53 validators: [required],
54 type: 'password',
55 },
56 },
57 }, this.context.intl);
58
59 submit(e) {
60 e.preventDefault();
61 this.form.submit({
62 onSuccess: (form) => {
63 this.props.onSubmit(form.values());
64 },
65 onError: () => { },
66 });
67 }
68
69 render() {
70 const { form } = this;
71 const { intl } = this.context;
72 const {
73 isSubmitting,
74 error,
75 } = this.props;
76
77 return (
78 <div className="auth__container">
79 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
80 <img
81 src="./assets/images/logo.svg"
82 className="auth__logo"
83 alt=""
84 />
85 <h1>{intl.formatMessage(messages.headline)}</h1>
86 <Infobox type="warning">
87 {intl.formatMessage(messages.info)}
88 </Infobox>
89 <Input
90 field={form.$('password')}
91 showPasswordToggle
92 />
93 {error.code === 'invalid-credentials' && (
94 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p>
95 )}
96 {isSubmitting ? (
97 <Button
98 className="auth__button is-loading"
99 buttonType="secondary"
100 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
101 loaded={false}
102 disabled
103 />
104 ) : (
105 <Button
106 type="submit"
107 className="auth__button"
108 label={intl.formatMessage(messages.submitButtonLabel)}
109 />
110 )}
111 </form>
112 </div>
113 );
114 }
115}
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
index 5d21f8b60..eea5a09bf 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -34,6 +34,14 @@ const messages = defineMessages({
34 id: 'login.invalidCredentials', 34 id: 'login.invalidCredentials',
35 defaultMessage: '!!!Email or password not valid', 35 defaultMessage: '!!!Email or password not valid',
36 }, 36 },
37 customServerQuestion: {
38 id: 'login.customServerQuestion',
39 defaultMessage: '!!!Using a Franz account to log in?',
40 },
41 customServerSuggestion: {
42 id: 'login.customServerSuggestion',
43 defaultMessage: '!!!Try importing your Franz account into Ferdi',
44 },
37 tokenExpired: { 45 tokenExpired: {
38 id: 'login.tokenExpired', 46 id: 'login.tokenExpired',
39 defaultMessage: '!!!Your session expired, please login again.', 47 defaultMessage: '!!!Your session expired, please login again.',
@@ -137,7 +145,22 @@ export default @observer class Login extends Component {
137 showPasswordToggle 145 showPasswordToggle
138 /> 146 />
139 {error.code === 'invalid-credentials' && ( 147 {error.code === 'invalid-credentials' && (
140 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p> 148 <>
149 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p>
150 { window.ferdi.stores.settings.all.app.server !== 'https://api.franzinfra.com' && (
151 <p className="error-message center">
152 {intl.formatMessage(messages.customServerQuestion)}
153 {' '}
154 <Link
155 to={`${window.ferdi.stores.settings.all.app.server.replace('v1', '')}/import`}
156 target="_blank"
157 style={{ cursor: 'pointer', textDecoration: 'underline' }}
158 >
159 {intl.formatMessage(messages.customServerSuggestion)}
160 </Link>
161 </p>
162 )}
163 </>
141 )} 164 )}
142 {isSubmitting ? ( 165 {isSubmitting ? (
143 <Button 166 <Button
@@ -156,6 +179,7 @@ export default @observer class Login extends Component {
156 )} 179 )}
157 </form> 180 </form>
158 <div className="auth__links"> 181 <div className="auth__links">
182 <Link to="/settings/app">Change server</Link>
159 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> 183 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
160 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link> 184 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link>
161 </div> 185 </div>
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index cbeaaa5d9..a77ad7742 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -42,7 +42,7 @@ const messages = defineMessages({
42 }, 42 },
43 ctaSkip: { 43 ctaSkip: {
44 id: 'pricing.trial.cta.skip', 44 id: 'pricing.trial.cta.skip',
45 defaultMessage: '!!!Continue to Franz', 45 defaultMessage: '!!!Continue to Ferdi',
46 }, 46 },
47 featuresHeadline: { 47 featuresHeadline: {
48 id: 'pricing.trial.features.headline', 48 id: 'pricing.trial.features.headline',
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index 0499d764b..b36e71ce1 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -40,7 +40,7 @@ const messages = defineMessages({
40 }, 40 },
41 legalInfo: { 41 legalInfo: {
42 id: 'signup.legal.info', 42 id: 'signup.legal.info',
43 defaultMessage: '!!!By creating a Franz account you accept the', 43 defaultMessage: '!!!By creating a Ferdi account you accept the',
44 }, 44 },
45 terms: { 45 terms: {
46 id: 'signup.legal.terms', 46 id: 'signup.legal.terms',
@@ -117,6 +117,8 @@ export default @observer class Signup extends Component {
117 const { intl } = this.context; 117 const { intl } = this.context;
118 const { isSubmitting, loginRoute, error } = this.props; 118 const { isSubmitting, loginRoute, error } = this.props;
119 119
120 const termsBase = window.ferdi.stores.settings.all.app.server !== 'https://api.franzinfra.com' ? window.ferdi.stores.settings.all.app.server : 'https://meetfranz.com';
121
120 return ( 122 return (
121 <div className="auth__scroll-container"> 123 <div className="auth__scroll-container">
122 <div className="auth__container auth__container--signup"> 124 <div className="auth__container auth__container--signup">
@@ -163,7 +165,7 @@ export default @observer class Signup extends Component {
163 {intl.formatMessage(messages.legalInfo)} 165 {intl.formatMessage(messages.legalInfo)}
164 <br /> 166 <br />
165 <Link 167 <Link
166 to="https://meetfranz.com/terms" 168 to={`${termsBase}/terms`}
167 target="_blank" 169 target="_blank"
168 className="link" 170 className="link"
169 > 171 >
@@ -171,7 +173,7 @@ export default @observer class Signup extends Component {
171 </Link> 173 </Link>
172 &nbsp;&amp;&nbsp; 174 &nbsp;&amp;&nbsp;
173 <Link 175 <Link
174 to="https://meetfranz.com/privacy" 176 to={`${termsBase}/privacy`}
175 target="_blank" 177 target="_blank"
176 className="link" 178 className="link"
177 > 179 >
@@ -181,6 +183,7 @@ export default @observer class Signup extends Component {
181 </p> 183 </p>
182 </form> 184 </form>
183 <div className="auth__links"> 185 <div className="auth__links">
186 <Link to="/settings/app">Change server</Link>
184 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> 187 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
185 </div> 188 </div>
186 </div> 189 </div>
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js
index f6d77f70f..ef917e336 100644
--- a/src/components/auth/Welcome.js
+++ b/src/components/auth/Welcome.js
@@ -41,7 +41,7 @@ export default @observer class Login extends Component {
41 <img src="./assets/images/logo.svg" className="welcome__logo" alt="" /> 41 <img src="./assets/images/logo.svg" className="welcome__logo" alt="" />
42 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */} 42 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */}
43 <div className="welcome__text"> 43 <div className="welcome__text">
44 <h1>Franz</h1> 44 <h1>Ferdi</h1>
45 </div> 45 </div>
46 </div> 46 </div>
47 <div className="welcome__buttons"> 47 <div className="welcome__buttons">
@@ -51,6 +51,19 @@ export default @observer class Login extends Component {
51 <Link to={loginRoute} className="button"> 51 <Link to={loginRoute} className="button">
52 {intl.formatMessage(messages.loginButton)} 52 {intl.formatMessage(messages.loginButton)}
53 </Link> 53 </Link>
54 <br />
55 <br />
56
57 <Link to="settings/app">
58 <span style={{
59 textAlign: 'center',
60 width: '100%',
61 cursor: 'pointer',
62 }}
63 >
64 Change server
65 </span>
66 </Link>
54 </div> 67 </div>
55 <div className="welcome__featured-services"> 68 <div className="welcome__featured-services">
56 {recipes.map(recipe => ( 69 {recipes.map(recipe => (
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 200777ae6..2b0719f92 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -6,9 +6,9 @@ import { TitleBar } from 'electron-react-titlebar';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7 7
8import InfoBar from '../ui/InfoBar'; 8import InfoBar from '../ui/InfoBar';
9import { Component as DelayApp } from '../../features/delayApp';
10import { Component as BasicAuth } from '../../features/basicAuth'; 9import { Component as BasicAuth } from '../../features/basicAuth';
11import { Component as ShareFranz } from '../../features/shareFranz'; 10import { Component as ShareFranz } from '../../features/shareFranz';
11import { Component as QuickSwitch } from '../../features/quickSwitch';
12import ErrorBoundary from '../util/ErrorBoundary'; 12import ErrorBoundary from '../util/ErrorBoundary';
13 13
14// import globalMessages from '../../i18n/globalMessages'; 14// import globalMessages from '../../i18n/globalMessages';
@@ -37,6 +37,10 @@ const messages = defineMessages({
37 id: 'infobar.requiredRequestsFailed', 37 id: 'infobar.requiredRequestsFailed',
38 defaultMessage: '!!!Could not load services and user information', 38 defaultMessage: '!!!Could not load services and user information',
39 }, 39 },
40 authRequestFailed: {
41 id: 'infobar.authRequestFailed',
42 defaultMessage: '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.',
43 },
40}); 44});
41 45
42const styles = theme => ({ 46const styles = theme => ({
@@ -63,6 +67,7 @@ class AppLayout extends Component {
63 showServicesUpdatedInfoBar: PropTypes.bool.isRequired, 67 showServicesUpdatedInfoBar: PropTypes.bool.isRequired,
64 appUpdateIsDownloaded: PropTypes.bool.isRequired, 68 appUpdateIsDownloaded: PropTypes.bool.isRequired,
65 nextAppReleaseVersion: PropTypes.string, 69 nextAppReleaseVersion: PropTypes.string,
70 authRequestFailed: PropTypes.bool.isRequired,
66 removeNewsItem: PropTypes.func.isRequired, 71 removeNewsItem: PropTypes.func.isRequired,
67 reloadServicesAfterUpdate: PropTypes.func.isRequired, 72 reloadServicesAfterUpdate: PropTypes.func.isRequired,
68 installAppUpdate: PropTypes.func.isRequired, 73 installAppUpdate: PropTypes.func.isRequired,
@@ -70,7 +75,6 @@ class AppLayout extends Component {
70 areRequiredRequestsSuccessful: PropTypes.bool.isRequired, 75 areRequiredRequestsSuccessful: PropTypes.bool.isRequired,
71 retryRequiredRequests: PropTypes.func.isRequired, 76 retryRequiredRequests: PropTypes.func.isRequired,
72 areRequiredRequestsLoading: PropTypes.bool.isRequired, 77 areRequiredRequestsLoading: PropTypes.bool.isRequired,
73 isDelayAppScreenVisible: PropTypes.bool.isRequired,
74 hasActivatedTrial: PropTypes.bool.isRequired, 78 hasActivatedTrial: PropTypes.bool.isRequired,
75 }; 79 };
76 80
@@ -95,6 +99,7 @@ class AppLayout extends Component {
95 showServicesUpdatedInfoBar, 99 showServicesUpdatedInfoBar,
96 appUpdateIsDownloaded, 100 appUpdateIsDownloaded,
97 nextAppReleaseVersion, 101 nextAppReleaseVersion,
102 authRequestFailed,
98 removeNewsItem, 103 removeNewsItem,
99 reloadServicesAfterUpdate, 104 reloadServicesAfterUpdate,
100 installAppUpdate, 105 installAppUpdate,
@@ -102,7 +107,6 @@ class AppLayout extends Component {
102 areRequiredRequestsSuccessful, 107 areRequiredRequestsSuccessful,
103 retryRequiredRequests, 108 retryRequiredRequests,
104 areRequiredRequestsLoading, 109 areRequiredRequestsLoading,
105 isDelayAppScreenVisible,
106 hasActivatedTrial, 110 hasActivatedTrial,
107 } = this.props; 111 } = this.props;
108 112
@@ -111,7 +115,7 @@ class AppLayout extends Component {
111 return ( 115 return (
112 <ErrorBoundary> 116 <ErrorBoundary>
113 <div className="app"> 117 <div className="app">
114 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 118 {isWindows && !isFullScreen && <TitleBar menu={window.ferdi.menu.template} icon="assets/images/logo.svg" />}
115 <div className={`app__content ${classes.appContent}`}> 119 <div className={`app__content ${classes.appContent}`}>
116 {workspacesDrawer} 120 {workspacesDrawer}
117 {sidebar} 121 {sidebar}
@@ -151,6 +155,18 @@ class AppLayout extends Component {
151 {intl.formatMessage(messages.requiredRequestsFailed)} 155 {intl.formatMessage(messages.requiredRequestsFailed)}
152 </InfoBar> 156 </InfoBar>
153 )} 157 )}
158 {authRequestFailed && (
159 <InfoBar
160 type="danger"
161 ctaLabel="Try again"
162 ctaLoading={areRequiredRequestsLoading}
163 sticky
164 onClick={retryRequiredRequests}
165 >
166 <span className="mdi mdi-flash" />
167 {intl.formatMessage(messages.authRequestFailed)}
168 </InfoBar>
169 )}
154 {showServicesUpdatedInfoBar && ( 170 {showServicesUpdatedInfoBar && (
155 <InfoBar 171 <InfoBar
156 type="primary" 172 type="primary"
@@ -168,9 +184,9 @@ class AppLayout extends Component {
168 onInstallUpdate={installAppUpdate} 184 onInstallUpdate={installAppUpdate}
169 /> 185 />
170 )} 186 )}
171 {isDelayAppScreenVisible && (<DelayApp />)}
172 <BasicAuth /> 187 <BasicAuth />
173 <ShareFranz /> 188 <ShareFranz />
189 <QuickSwitch />
174 {services} 190 {services}
175 {children} 191 {children}
176 </div> 192 </div>
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 918298011..d0cae3443 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -2,13 +2,13 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import ReactTooltip from 'react-tooltip'; 3import ReactTooltip from 'react-tooltip';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { observer } from 'mobx-react'; 5import { inject, observer } from 'mobx-react';
6import { Link } from 'react-router';
6 7
7import Tabbar from '../services/tabs/Tabbar'; 8import Tabbar from '../services/tabs/Tabbar';
8import { ctrlKey } from '../../environment'; 9import { ctrlKey } from '../../environment';
9import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../../features/workspaces'; 10import { workspaceStore } from '../../features/workspaces';
10import { gaEvent } from '../../lib/analytics'; 11import { todosStore } from '../../features/todos';
11import { todosStore, GA_CATEGORY_TODOS } from '../../features/todos';
12import { todoActions } from '../../features/todos/actions'; 12import { todoActions } from '../../features/todos/actions';
13 13
14const messages = defineMessages({ 14const messages = defineMessages({
@@ -44,9 +44,13 @@ const messages = defineMessages({
44 id: 'sidebar.closeTodosDrawer', 44 id: 'sidebar.closeTodosDrawer',
45 defaultMessage: '!!!Close Franz Todos', 45 defaultMessage: '!!!Close Franz Todos',
46 }, 46 },
47 lockFerdi: {
48 id: 'sidebar.lockFerdi',
49 defaultMessage: '!!!Lock Ferdi',
50 },
47}); 51});
48 52
49export default @observer class Sidebar extends Component { 53export default @inject('stores', 'actions') @observer class Sidebar extends Component {
50 static propTypes = { 54 static propTypes = {
51 openSettings: PropTypes.func.isRequired, 55 openSettings: PropTypes.func.isRequired,
52 toggleMuteApp: PropTypes.func.isRequired, 56 toggleMuteApp: PropTypes.func.isRequired,
@@ -87,6 +91,8 @@ export default @observer class Sidebar extends Component {
87 isAppMuted, 91 isAppMuted,
88 isWorkspaceDrawerOpen, 92 isWorkspaceDrawerOpen,
89 toggleWorkspaceDrawer, 93 toggleWorkspaceDrawer,
94 stores,
95 actions,
90 } = this.props; 96 } = this.props;
91 const { intl } = this.context; 97 const { intl } = this.context;
92 const todosToggleMessage = ( 98 const todosToggleMessage = (
@@ -96,6 +102,7 @@ export default @observer class Sidebar extends Component {
96 const workspaceToggleMessage = ( 102 const workspaceToggleMessage = (
97 isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer 103 isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer
98 ); 104 );
105 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
99 106
100 return ( 107 return (
101 <div className="sidebar"> 108 <div className="sidebar">
@@ -104,53 +111,89 @@ export default @observer class Sidebar extends Component {
104 enableToolTip={() => this.enableToolTip()} 111 enableToolTip={() => this.enableToolTip()}
105 disableToolTip={() => this.disableToolTip()} 112 disableToolTip={() => this.disableToolTip()}
106 /> 113 />
107 {todosStore.isFeatureEnabled && todosStore.isFeatureEnabledByUser ? ( 114 { isLoggedIn ? (
108 <button 115 <>
109 type="button" 116 { stores.settings.all.app.lockingFeatureEnabled ? (
110 onClick={() => { 117 <button
111 todoActions.toggleTodosPanel(); 118 type="button"
112 this.updateToolTip(); 119 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
113 gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'sidebar'); 120 onClick={() => {
114 }} 121 // Disable lock first - otherwise the application might not update correctly
115 className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} 122 actions.settings.update({
116 data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`} 123 type: 'app',
117 > 124 data: {
118 <i className="mdi mdi-check-all" /> 125 locked: false,
119 </button> 126 },
120 ) : null} 127 });
121 {workspaceStore.isFeatureEnabled ? ( 128 setTimeout(() => {
122 <button 129 actions.settings.update({
123 type="button" 130 type: 'app',
124 onClick={() => { 131 data: {
125 toggleWorkspaceDrawer(); 132 locked: true,
126 this.updateToolTip(); 133 },
127 gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'sidebar'); 134 });
128 }} 135 }, 0);
129 className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`} 136 }}
130 data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${ctrlKey}+D)`} 137 data-tip={`${intl.formatMessage(messages.lockFerdi)} (${ctrlKey}+Shift+L)`}
138 >
139 <i className="mdi mdi-lock" />
140 </button>
141 ) : null}
142 {todosStore.isFeatureEnabled && todosStore.isFeatureEnabledByUser ? (
143 <button
144 type="button"
145 onClick={() => {
146 todoActions.toggleTodosPanel();
147 this.updateToolTip();
148 }}
149 className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`}
150 data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`}
151 >
152 <i className="mdi mdi-check-all" />
153 </button>
154 ) : null}
155 {workspaceStore.isFeatureEnabled ? (
156 <button
157 type="button"
158 onClick={() => {
159 toggleWorkspaceDrawer();
160 this.updateToolTip();
161 }}
162 className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`}
163 data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${ctrlKey}+D)`}
164 >
165 <i className="mdi mdi-view-grid" />
166 </button>
167 ) : null}
168 <button
169 type="button"
170 onClick={() => {
171 toggleMuteApp();
172 this.updateToolTip();
173 }}
174 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
175 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
176 >
177 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
178 </button>
179 <button
180 type="button"
181 onClick={() => openSettings({ path: 'recipes' })}
182 className="sidebar__button sidebar__button--new-service"
183 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
184 >
185 <i className="mdi mdi-plus-box" />
186 </button>
187 </>
188 ) : (
189 <Link
190 to="/auth/welcome"
191 className="sidebar__button sidebar__button--new-service"
192 data-tip="Login"
131 > 193 >
132 <i className="mdi mdi-view-grid" /> 194 <i className="mdi mdi-login-variant" />
133 </button> 195 </Link>
134 ) : null} 196 )}
135 <button
136 type="button"
137 onClick={() => {
138 toggleMuteApp();
139 this.updateToolTip();
140 }}
141 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
142 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
143 >
144 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
145 </button>
146 <button
147 type="button"
148 onClick={() => openSettings({ path: 'recipes' })}
149 className="sidebar__button sidebar__button--new-service"
150 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
151 >
152 <i className="mdi mdi-plus-box" />
153 </button>
154 <button 197 <button
155 type="button" 198 type="button"
156 onClick={() => openSettings({ path: 'app' })} 199 onClick={() => openSettings({ path: 'app' })}
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 3b09518c5..273653ea2 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -1,7 +1,7 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { autorun } from 'mobx'; 3import { autorun, reaction } from 'mobx';
4import { observer } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
7import ServiceModel from '../../../models/Service'; 7import ServiceModel from '../../../models/Service';
@@ -10,12 +10,12 @@ import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler'; 10import WebviewCrashHandler from './WebviewCrashHandler';
11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; 11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
12import ServiceDisabled from './ServiceDisabled'; 12import ServiceDisabled from './ServiceDisabled';
13import ServiceRestricted from './ServiceRestricted';
14import ServiceWebview from './ServiceWebview'; 13import ServiceWebview from './ServiceWebview';
14import SettingsStore from '../../../stores/SettingsStore';
15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; 15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
16import { CUSTOM_WEBSITE_ID } from '../../../features/webControls/constants'; 16import { CUSTOM_WEBSITE_ID } from '../../../features/webControls/constants';
17 17
18export default @observer class ServiceView extends Component { 18export default @observer @inject('stores') class ServiceView extends Component {
19 static propTypes = { 19 static propTypes = {
20 service: PropTypes.instanceOf(ServiceModel).isRequired, 20 service: PropTypes.instanceOf(ServiceModel).isRequired,
21 setWebviewReference: PropTypes.func.isRequired, 21 setWebviewReference: PropTypes.func.isRequired,
@@ -24,7 +24,9 @@ export default @observer class ServiceView extends Component {
24 edit: PropTypes.func.isRequired, 24 edit: PropTypes.func.isRequired,
25 enable: PropTypes.func.isRequired, 25 enable: PropTypes.func.isRequired,
26 isActive: PropTypes.bool, 26 isActive: PropTypes.bool,
27 upgrade: PropTypes.func.isRequired, 27 stores: PropTypes.shape({
28 settings: PropTypes.instanceOf(SettingsStore).isRequired,
29 }).isRequired,
28 }; 30 };
29 31
30 static defaultProps = { 32 static defaultProps = {
@@ -35,12 +37,20 @@ export default @observer class ServiceView extends Component {
35 forceRepaint: false, 37 forceRepaint: false,
36 targetUrl: '', 38 targetUrl: '',
37 statusBarVisible: false, 39 statusBarVisible: false,
40 hibernate: false,
41 hibernationTimer: null,
38 }; 42 };
39 43
40 autorunDisposer = null; 44 autorunDisposer = null;
41 45
42 forceRepaintTimeout = null; 46 forceRepaintTimeout = null;
43 47
48 constructor(props) {
49 super(props);
50
51 this.startHibernationTimer = this.startHibernationTimer.bind(this);
52 }
53
44 componentDidMount() { 54 componentDidMount() {
45 this.autorunDisposer = autorun(() => { 55 this.autorunDisposer = autorun(() => {
46 if (this.props.service.isActive) { 56 if (this.props.service.isActive) {
@@ -50,6 +60,31 @@ export default @observer class ServiceView extends Component {
50 }, 100); 60 }, 100);
51 } 61 }
52 }); 62 });
63
64 reaction(
65 () => this.props.service.isActive,
66 () => {
67 if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) {
68 // Service is inactive - start hibernation countdown
69 this.startHibernationTimer();
70 } else {
71 if (this.state.hibernationTimer) {
72 // Service is active but we have an active hibernation timer: Clear timeout
73 clearTimeout(this.state.hibernationTimer);
74 }
75
76 // Service is active, wake up service from hibernation
77 this.setState({
78 hibernate: false,
79 });
80 }
81 },
82 );
83
84 // Start hibernation counter if we are in background
85 if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) {
86 this.startHibernationTimer();
87 }
53 } 88 }
54 89
55 componentWillUnmount() { 90 componentWillUnmount() {
@@ -68,6 +103,20 @@ export default @observer class ServiceView extends Component {
68 }); 103 });
69 }; 104 };
70 105
106 startHibernationTimer() {
107 const timerDuration = (Number(this.props.stores.settings.all.app.hibernationStrategy) || 300) * 1000;
108
109 const hibernationTimer = setTimeout(() => {
110 this.setState({
111 hibernate: true,
112 });
113 }, timerDuration);
114
115 this.setState({
116 hibernationTimer,
117 });
118 }
119
71 render() { 120 render() {
72 const { 121 const {
73 detachService, 122 detachService,
@@ -76,7 +125,6 @@ export default @observer class ServiceView extends Component {
76 reload, 125 reload,
77 edit, 126 edit,
78 enable, 127 enable,
79 upgrade,
80 } = this.props; 128 } = this.props;
81 129
82 const webviewClasses = classnames({ 130 const webviewClasses = classnames({
@@ -132,13 +180,10 @@ export default @observer class ServiceView extends Component {
132 </Fragment> 180 </Fragment>
133 ) : ( 181 ) : (
134 <> 182 <>
135 {service.isServiceAccessRestricted ? ( 183 {service.recipe.id === 'franz-custom-website' && (
136 <ServiceRestricted 184 <WebControlsScreen service={service} />
137 name={service.recipe.name} 185 )}
138 upgrade={upgrade} 186 {!this.state.hibernate ? (
139 type={service.restrictionType}
140 />
141 ) : (
142 <> 187 <>
143 {service.recipe.id === CUSTOM_WEBSITE_ID && ( 188 {service.recipe.id === CUSTOM_WEBSITE_ID && (
144 <WebControlsScreen service={service} /> 189 <WebControlsScreen service={service} />
@@ -149,6 +194,12 @@ export default @observer class ServiceView extends Component {
149 detachService={detachService} 194 detachService={detachService}
150 /> 195 />
151 </> 196 </>
197 ) : (
198 <div>
199 <span role="img" aria-label="Sleeping Emoji">😴</span>
200 {' '}
201 This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi.
202 </div>
152 )} 203 )}
153 </> 204 </>
154 )} 205 )}
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 4bab4a964..e6ebb6afb 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -1,10 +1,13 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { observable, reaction } from 'mobx';
4import ElectronWebView from 'react-electron-web-view'; 5import ElectronWebView from 'react-electron-web-view';
5 6
6import ServiceModel from '../../../models/Service'; 7import ServiceModel from '../../../models/Service';
7 8
9const debug = require('debug')('Ferdi:Services');
10
8@observer 11@observer
9class ServiceWebview extends Component { 12class ServiceWebview extends Component {
10 static propTypes = { 13 static propTypes = {
@@ -13,7 +16,22 @@ class ServiceWebview extends Component {
13 detachService: PropTypes.func.isRequired, 16 detachService: PropTypes.func.isRequired,
14 }; 17 };
15 18
16 webview = null; 19 @observable webview = null;
20
21 constructor(props) {
22 super(props);
23
24 reaction(
25 () => this.webview,
26 () => {
27 if (this.webview && this.webview.view) {
28 this.webview.view.addEventListener('console-message', (e) => {
29 debug('Service logged a message:', e.message);
30 });
31 }
32 },
33 );
34 }
17 35
18 componentWillUnmount() { 36 componentWillUnmount() {
19 const { service, detachService } = this.props; 37 const { service, detachService } = this.props;
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index b6291666b..edff29ae8 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -13,12 +13,20 @@ import Appear from '../../ui/effects/Appear';
13const messages = defineMessages({ 13const messages = defineMessages({
14 welcome: { 14 welcome: {
15 id: 'services.welcome', 15 id: 'services.welcome',
16 defaultMessage: '!!!Welcome to Franz', 16 defaultMessage: '!!!Welcome to Ferdi',
17 }, 17 },
18 getStarted: { 18 getStarted: {
19 id: 'services.getStarted', 19 id: 'services.getStarted',
20 defaultMessage: '!!!Get started', 20 defaultMessage: '!!!Get started',
21 }, 21 },
22 login: {
23 id: 'services.login',
24 defaultMessage: '!!!Please login to use Ferdi.',
25 },
26 serverInfo: {
27 id: 'services.serverInfo',
28 defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.',
29 },
22}); 30});
23 31
24 32
@@ -94,6 +102,7 @@ export default @observer @injectSheet(styles) class Services extends Component {
94 } = this.state; 102 } = this.state;
95 103
96 const { intl } = this.context; 104 const { intl } = this.context;
105 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
97 106
98 return ( 107 return (
99 <div className="services"> 108 <div className="services">
@@ -112,14 +121,20 @@ export default @observer @injectSheet(styles) class Services extends Component {
112 transitionName="slideUp" 121 transitionName="slideUp"
113 > 122 >
114 <div className="services__no-service"> 123 <div className="services__no-service">
115 <img src="./assets/images/logo.svg" alt="" /> 124 <img src="./assets/images/logo.svg" alt="Logo" style={{ maxHeight: '50vh' }} />
116 <h1>{intl.formatMessage(messages.welcome)}</h1> 125 <h1>{intl.formatMessage(messages.welcome)}</h1>
126 { !isLoggedIn && (
127 <>
128 <p>{intl.formatMessage(messages.login)}</p>
129 <p>{intl.formatMessage(messages.serverInfo)}</p>
130 </>
131 ) }
117 <Appear 132 <Appear
118 timeout={300} 133 timeout={300}
119 transitionName="slideUp" 134 transitionName="slideUp"
120 > 135 >
121 <Link to="/settings/recipes" className="button"> 136 <Link to={isLoggedIn ? '/settings/services' : '/auth/welcome'} className="button">
122 {intl.formatMessage(messages.getStarted)} 137 { isLoggedIn ? intl.formatMessage(messages.getStarted) : 'Login' }
123 </Link> 138 </Link>
124 </Appear> 139 </Appear>
125 </div> 140 </div>
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index ac2594604..f588449f4 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -69,7 +69,7 @@ const messages = defineMessages({
69 }, 69 },
70 deleteInfo: { 70 deleteInfo: {
71 id: 'settings.account.deleteInfo', 71 id: 'settings.account.deleteInfo',
72 defaultMessage: '!!!If you don\'t need your Franz account any longer, you can delete your account and all related data here.', 72 defaultMessage: '!!!If you don\'t need your Ferdi account any longer, you can delete your account and all related data here.',
73 }, 73 },
74 deleteEmailSent: { 74 deleteEmailSent: {
75 id: 'settings.account.deleteEmailSent', 75 id: 'settings.account.deleteEmailSent',
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 4696b82eb..2711bc107 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -35,9 +35,9 @@ const messages = defineMessages({
35 id: 'settings.navigation.settings', 35 id: 'settings.navigation.settings',
36 defaultMessage: '!!!Settings', 36 defaultMessage: '!!!Settings',
37 }, 37 },
38 inviteFriends: { 38 supportFerdi: {
39 id: 'settings.navigation.inviteFriends', 39 id: 'settings.navigation.supportFerdi',
40 defaultMessage: '!!!Invite Friends', 40 defaultMessage: '!!!Support Ferdi',
41 }, 41 },
42 logout: { 42 logout: {
43 id: 'settings.navigation.logout', 43 id: 'settings.navigation.logout',
@@ -64,6 +64,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
64 const { isDarkThemeActive } = stores.ui; 64 const { isDarkThemeActive } = stores.ui;
65 const { router, user } = stores; 65 const { router, user } = stores;
66 const { intl } = this.context; 66 const { intl } = this.context;
67 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
67 68
68 return ( 69 return (
69 <div className="settings-navigation"> 70 <div className="settings-navigation">
@@ -128,18 +129,18 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
128 {intl.formatMessage(messages.settings)} 129 {intl.formatMessage(messages.settings)}
129 </Link> 130 </Link>
130 <Link 131 <Link
131 to="/settings/invite" 132 to="/settings/support"
132 className="settings-navigation__link" 133 className="settings-navigation__link"
133 activeClassName="is-active" 134 activeClassName="is-active"
134 > 135 >
135 {intl.formatMessage(messages.inviteFriends)} 136 {intl.formatMessage(messages.supportFerdi)}
136 </Link> 137 </Link>
137 <span className="settings-navigation__expander" /> 138 <span className="settings-navigation__expander" />
138 <Link 139 <Link
139 to="/auth/logout" 140 to={isLoggedIn ? '/auth/logout' : '/auth/welcome'}
140 className="settings-navigation__link" 141 className="settings-navigation__link"
141 > 142 >
142 {intl.formatMessage(messages.logout)} 143 { isLoggedIn ? intl.formatMessage(messages.logout) : 'Login'}
143 </Link> 144 </Link>
144 </div> 145 </div>
145 ); 146 );
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 5cde0db8e..5fe00cb8b 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -63,7 +63,7 @@ const messages = defineMessages({
63 }, 63 },
64 customUrlPremiumInfo: { 64 customUrlPremiumInfo: {
65 id: 'settings.service.form.customUrlPremiumInfo', 65 id: 'settings.service.form.customUrlPremiumInfo',
66 defaultMessage: '!!!To add self hosted services, you need a Franz Premium Supporter Account.', 66 defaultMessage: '!!!To add self hosted services, you need a Ferdi Premium Supporter Account.',
67 }, 67 },
68 customUrlUpgradeAccount: { 68 customUrlUpgradeAccount: {
69 id: 'settings.service.form.customUrlUpgradeAccount', 69 id: 'settings.service.form.customUrlUpgradeAccount',
@@ -103,11 +103,11 @@ const messages = defineMessages({
103 }, 103 },
104 proxyRestartInfo: { 104 proxyRestartInfo: {
105 id: 'settings.service.form.proxy.restartInfo', 105 id: 'settings.service.form.proxy.restartInfo',
106 defaultMessage: '!!!Please restart Franz after changing proxy Settings.', 106 defaultMessage: '!!!Please restart Ferdi after changing proxy Settings.',
107 }, 107 },
108 proxyInfo: { 108 proxyInfo: {
109 id: 'settings.service.form.proxy.info', 109 id: 'settings.service.form.proxy.info',
110 defaultMessage: '!!!Proxy settings will not be synchronized with the Franz servers.', 110 defaultMessage: '!!!Proxy settings will not be synchronized with the Ferdi servers.',
111 }, 111 },
112}); 112});
113 113
@@ -155,7 +155,7 @@ export default @observer class EditServiceForm extends Component {
155 const values = form.values(); 155 const values = form.values();
156 let isValid = true; 156 let isValid = true;
157 157
158 const files = form.$('customIcon').files; 158 const { files } = form.$('customIcon');
159 if (files) { 159 if (files) {
160 values.iconFile = files[0]; 160 values.iconFile = files[0];
161 } 161 }
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 0b69f7514..1030f3164 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -9,9 +9,19 @@ import Button from '../../ui/Button';
9import Toggle from '../../ui/Toggle'; 9import Toggle from '../../ui/Toggle';
10import Select from '../../ui/Select'; 10import Select from '../../ui/Select';
11import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; 11import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
12import Input from '../../ui/Input';
12 13
13import { FRANZ_TRANSLATION } from '../../../config'; 14import { FRANZ_TRANSLATION } from '../../../config';
14 15
16function escapeHtml(unsafe) {
17 return unsafe
18 .replace(/&/g, '&amp;')
19 .replace(/</g, '&lt;')
20 .replace(/>/g, '&gt;')
21 .replace(/"/g, '&quot;')
22 .replace(/'/g, '&#039;');
23}
24
15const messages = defineMessages({ 25const messages = defineMessages({
16 headline: { 26 headline: {
17 id: 'settings.app.headline', 27 id: 'settings.app.headline',
@@ -21,6 +31,42 @@ const messages = defineMessages({
21 id: 'settings.app.headlineGeneral', 31 id: 'settings.app.headlineGeneral',
22 defaultMessage: '!!!General', 32 defaultMessage: '!!!General',
23 }, 33 },
34 hibernateInfo: {
35 id: 'settings.app.hibernateInfo',
36 defaultMessage: '!!!By default, Ferdi will keep all your services open and loaded in the background so they are ready when you want to use them. Service Hibernation will unload your services after a specified amount. This is useful to save RAM or keeping services from slowing down your computer.',
37 },
38 serverInfo: {
39 id: 'settings.app.serverInfo',
40 defaultMessage: '!!!We advice you to logout after changing your server as your settings might not be saved otherwise.',
41 },
42 serverMoneyInfo: {
43 id: 'settings.app.serverMoneyInfo',
44 defaultMessage: '!!!You are using the official Franz Server for Ferdi.\nWe know that Ferdi allows you to use all its features for free but you are still using Franz\'s server resources - which Franz\'s creator has to pay for.\nPlease still consider [Link 1]paying for a Franz account[/Link] or [Link 2]using a self-hosted ferdi-server[/Link] (if you have the knowledge and resources to do so). \nBy using Ferdi, you still profit greatly from Franz\'s recipe store, server resources and its development.',
45 },
46 todoServerInfo: {
47 id: 'settings.app.todoServerInfo',
48 defaultMessage: '!!!This server will be used for the "Franz Todo" feature. (default: https://app.franztodos.com)',
49 },
50 lockedPassword: {
51 id: 'settings.app.lockedPassword',
52 defaultMessage: '!!!Ferdi Lock Password',
53 },
54 lockedPasswordInfo: {
55 id: 'settings.app.lockedPasswordInfo',
56 defaultMessage: '!!!Please make sure to set a password you\'ll remember.\nIf you loose this password, you will have to reinstall Ferdi.',
57 },
58 lockInfo: {
59 id: 'settings.app.lockInfo',
60 defaultMessage: '!!!Ferdi password lock allows you to keep your messages protected.\nUsing Ferdi password lock, you will be prompted to enter your password everytime you start Ferdi or lock Ferdi yourself using the lock symbol in the bottom left corner or the shortcut CMD/CTRL+Shift+L.',
61 },
62 scheduledDNDTimeInfo: {
63 id: 'settings.app.scheduledDNDTimeInfo',
64 defaultMessage: '!!!Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.',
65 },
66 scheduledDNDInfo: {
67 id: 'settings.app.scheduledDNDInfo',
68 defaultMessage: '!!!Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.',
69 },
24 headlineLanguage: { 70 headlineLanguage: {
25 id: 'settings.app.headlineLanguage', 71 id: 'settings.app.headlineLanguage',
26 defaultMessage: '!!!Language', 72 defaultMessage: '!!!Language',
@@ -39,7 +85,7 @@ const messages = defineMessages({
39 }, 85 },
40 translationHelp: { 86 translationHelp: {
41 id: 'settings.app.translationHelp', 87 id: 'settings.app.translationHelp',
42 defaultMessage: '!!!Help us to translate Franz into your language.', 88 defaultMessage: '!!!Help us to translate Ferdi into your language.',
43 }, 89 },
44 subheadlineCache: { 90 subheadlineCache: {
45 id: 'settings.app.subheadlineCache', 91 id: 'settings.app.subheadlineCache',
@@ -47,7 +93,7 @@ const messages = defineMessages({
47 }, 93 },
48 cacheInfo: { 94 cacheInfo: {
49 id: 'settings.app.cacheInfo', 95 id: 'settings.app.cacheInfo',
50 defaultMessage: '!!!Franz cache is currently using {size} of disk space.', 96 defaultMessage: '!!!Ferdi cache is currently using {size} of disk space.',
51 }, 97 },
52 buttonClearAllCache: { 98 buttonClearAllCache: {
53 id: 'settings.app.buttonClearAllCache', 99 id: 'settings.app.buttonClearAllCache',
@@ -71,7 +117,7 @@ const messages = defineMessages({
71 }, 117 },
72 updateStatusUpToDate: { 118 updateStatusUpToDate: {
73 id: 'settings.app.updateStatusUpToDate', 119 id: 'settings.app.updateStatusUpToDate',
74 defaultMessage: '!!!You are using the latest version of Franz', 120 defaultMessage: '!!!You are using the latest version of Ferdi',
75 }, 121 },
76 currentVersion: { 122 currentVersion: {
77 id: 'settings.app.currentVersion', 123 id: 'settings.app.currentVersion',
@@ -103,6 +149,10 @@ export default @observer class EditSettingsForm extends Component {
103 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, 149 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
104 isTodosEnabled: PropTypes.bool.isRequired, 150 isTodosEnabled: PropTypes.bool.isRequired,
105 isWorkspaceEnabled: PropTypes.bool.isRequired, 151 isWorkspaceEnabled: PropTypes.bool.isRequired,
152 server: PropTypes.string.isRequired,
153 noUpdates: PropTypes.bool.isRequired,
154 hibernationEnabled: PropTypes.bool.isRequired,
155 openProcessManager: PropTypes.func.isRequired,
106 }; 156 };
107 157
108 static contextTypes = { 158 static contextTypes = {
@@ -135,6 +185,10 @@ export default @observer class EditSettingsForm extends Component {
135 isSpellcheckerIncludedInCurrentPlan, 185 isSpellcheckerIncludedInCurrentPlan,
136 isTodosEnabled, 186 isTodosEnabled,
137 isWorkspaceEnabled, 187 isWorkspaceEnabled,
188 server,
189 noUpdates,
190 hibernationEnabled,
191 openProcessManager,
138 } = this.props; 192 } = this.props;
139 const { intl } = this.context; 193 const { intl } = this.context;
140 194
@@ -147,6 +201,13 @@ export default @observer class EditSettingsForm extends Component {
147 updateButtonLabelMessage = messages.buttonSearchForUpdate; 201 updateButtonLabelMessage = messages.buttonSearchForUpdate;
148 } 202 }
149 203
204 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
205
206 const {
207 lockingFeatureEnabled,
208 scheduledDNDEnabled,
209 } = window.ferdi.stores.settings.all.app;
210
150 return ( 211 return (
151 <div className="settings__main"> 212 <div className="settings__main">
152 <div className="settings__header"> 213 <div className="settings__header">
@@ -163,15 +224,148 @@ export default @observer class EditSettingsForm extends Component {
163 <Toggle field={form.$('autoLaunchOnStart')} /> 224 <Toggle field={form.$('autoLaunchOnStart')} />
164 <Toggle field={form.$('runInBackground')} /> 225 <Toggle field={form.$('runInBackground')} />
165 <Toggle field={form.$('enableSystemTray')} /> 226 <Toggle field={form.$('enableSystemTray')} />
227 <Toggle field={form.$('privateNotifications')} />
228 <Toggle field={form.$('hibernate')} />
229 {hibernationEnabled && (
230 <Select field={form.$('hibernationStrategy')} />
231 )}
232 <p
233 className="settings__message"
234 style={{
235 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
236 }}
237 >
238 <span>
239 { intl.formatMessage(messages.hibernateInfo) }
240 </span>
241 </p>
166 {process.platform === 'win32' && ( 242 {process.platform === 'win32' && (
167 <Toggle field={form.$('minimizeToSystemTray')} /> 243 <Toggle field={form.$('minimizeToSystemTray')} />
168 )} 244 )}
245 <Input
246 placeholder="Server"
247 onChange={e => this.submit(e)}
248 field={form.$('server')}
249 autoFocus
250 />
251 {isLoggedIn && (
252 <p>{ intl.formatMessage(messages.serverInfo) }</p>
253 )}
254 {server === 'https://api.franzinfra.com' && (
255 <p
256 className="settings__message"
257 style={{
258 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
259 }}
260 >
261 <span
262 dangerouslySetInnerHTML={{
263 __html:
264 // Needed to make links work
265 escapeHtml(
266 intl.formatMessage(messages.serverMoneyInfo),
267 ).replace('[Link 1]', '<a href="https://www.meetfranz.com/pricing" target="_blank">')
268 .replace('[Link 2]', '<a href="https://github.com/getferdi/server" target="_blank">')
269 .replace(/\[\/Link]/g, '</a>'),
270 }}
271 style={{
272 whiteSpace: 'pre-wrap',
273 }}
274 />
275 </p>
276 )}
169 {isWorkspaceEnabled && ( 277 {isWorkspaceEnabled && (
170 <Toggle field={form.$('keepAllWorkspacesLoaded')} /> 278 <Toggle field={form.$('keepAllWorkspacesLoaded')} />
171 )} 279 )}
172 {isTodosEnabled && ( 280 {isTodosEnabled && (
173 <Toggle field={form.$('enableTodos')} /> 281 <>
282 <Toggle field={form.$('enableTodos')} />
283 <Input
284 placeholder="Todo Server"
285 onChange={e => this.submit(e)}
286 field={form.$('todoServer')}
287 />
288 <p>{ intl.formatMessage(messages.todoServerInfo) }</p>
289 </>
290 )}
291
292 <Toggle field={form.$('lockingFeatureEnabled')} />
293 {lockingFeatureEnabled && (
294 <>
295 <Input
296 placeholder={intl.formatMessage(messages.lockedPassword)}
297 onChange={e => this.submit(e)}
298 field={form.$('lockedPassword')}
299 type="password"
300 scorePassword
301 showPasswordToggle
302 />
303 <p>
304 { intl.formatMessage(messages.lockedPasswordInfo) }
305 </p>
306 </>
174 )} 307 )}
308 <p
309 className="settings__message"
310 style={{
311 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
312 }}
313 >
314 <span>
315 { intl.formatMessage(messages.lockInfo) }
316 </span>
317 </p>
318
319
320 <Toggle field={form.$('scheduledDNDEnabled')} />
321 {scheduledDNDEnabled && (
322 <>
323 <div style={{
324 display: 'flex',
325 justifyContent: 'center',
326 }}
327 >
328 <div style={{
329 padding: '0 1rem',
330 width: '100%',
331 }}
332 >
333 <Input
334 placeholder="17:00"
335 onChange={e => this.submit(e)}
336 field={form.$('scheduledDNDStart')}
337 type="time"
338 />
339 </div>
340 <div style={{
341 padding: '0 1rem',
342 width: '100%',
343 }}
344 >
345 <Input
346 placeholder="09:00"
347 onChange={e => this.submit(e)}
348 field={form.$('scheduledDNDEnd')}
349 type="time"
350 />
351 </div>
352 </div>
353 <p>
354 { intl.formatMessage(messages.scheduledDNDTimeInfo) }
355 </p>
356 </>
357 )}
358 <p
359 className="settings__message"
360 style={{
361 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
362 }}
363 >
364 <span>
365 { intl.formatMessage(messages.scheduledDNDInfo) }
366 </span>
367 </p>
368
175 369
176 {/* Appearance */} 370 {/* Appearance */}
177 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2> 371 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2>
@@ -227,6 +421,16 @@ export default @observer class EditSettingsForm extends Component {
227 loaded={!isClearingAllCache} 421 loaded={!isClearingAllCache}
228 /> 422 />
229 </p> 423 </p>
424 <div style={{
425 marginTop: 20,
426 }}
427 >
428 <Button
429 buttonType="secondary"
430 label="Open Process Manager"
431 onClick={openProcessManager}
432 />
433 </div>
230 </div> 434 </div>
231 435
232 {/* Updates */} 436 {/* Updates */}
@@ -241,7 +445,7 @@ export default @observer class EditSettingsForm extends Component {
241 buttonType="secondary" 445 buttonType="secondary"
242 label={intl.formatMessage(updateButtonLabelMessage)} 446 label={intl.formatMessage(updateButtonLabelMessage)}
243 onClick={checkForUpdates} 447 onClick={checkForUpdates}
244 disabled={isCheckingForUpdates || isUpdateAvailable} 448 disabled={noUpdates || isCheckingForUpdates || isUpdateAvailable}
245 loaded={!isCheckingForUpdates || !isUpdateAvailable} 449 loaded={!isCheckingForUpdates || !isUpdateAvailable}
246 /> 450 />
247 )} 451 )}
@@ -250,6 +454,7 @@ export default @observer class EditSettingsForm extends Component {
250 )} 454 )}
251 <br /> 455 <br />
252 <Toggle field={form.$('beta')} /> 456 <Toggle field={form.$('beta')} />
457 <Toggle field={form.$('noUpdates')} />
253 {intl.formatMessage(messages.currentVersion)} 458 {intl.formatMessage(messages.currentVersion)}
254 {' '} 459 {' '}
255 {remote.app.getVersion()} 460 {remote.app.getVersion()}
@@ -257,6 +462,18 @@ export default @observer class EditSettingsForm extends Component {
257 <span className="mdi mdi-information" /> 462 <span className="mdi mdi-information" />
258 {intl.formatMessage(messages.languageDisclaimer)} 463 {intl.formatMessage(messages.languageDisclaimer)}
259 </p> 464 </p>
465 <p className="settings__message">
466 <span className="mdi mdi-github-face" />
467 <span>
468 Ferdi is based on
469 {' '}
470 <a href="https://github.com/meetfranz/franz" target="_blank">Franz</a>
471 , a project published
472 under the
473 {' '}
474 <a href="https://github.com/meetfranz/franz/blob/master/LICENSE" target="_blank">Apache-2.0 License</a>
475 </span>
476 </p>
260 </form> 477 </form>
261 </div> 478 </div>
262 </div> 479 </div>
diff --git a/src/components/settings/supportFerdi/SupportFerdiDashboard.js b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
new file mode 100644
index 000000000..57920a4a2
--- /dev/null
+++ b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
@@ -0,0 +1,73 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4
5import Button from '../../ui/Button';
6
7const messages = defineMessages({
8 headline: {
9 id: 'settings.supportFerdi.headline',
10 defaultMessage: '!!!Support Ferdi',
11 },
12 title: {
13 id: 'settings.supportFerdi.title',
14 defaultMessage: '!!!Do you like Ferdi? Spread the love!',
15 },
16 github: {
17 id: 'settings.supportFerdi.github',
18 defaultMessage: '!!!Star on GitHub',
19 },
20 share: {
21 id: 'settings.supportFerdi.share',
22 defaultMessage: '!!!Tell your Friends',
23 },
24 openCollective: {
25 id: 'settings.supportFerdi.openCollective',
26 defaultMessage: '!!!Support our Open Collective',
27 },
28});
29
30class SupportFerdiDashboard extends Component {
31 static contextTypes = {
32 intl: intlShape,
33 };
34
35 static propTypes = {
36 openLink: PropTypes.func.isRequired,
37 };
38
39 render() {
40 const { openLink } = this.props;
41 const { intl } = this.context;
42
43 return (
44 <div className="settings__main">
45 <div className="settings__header">
46 <span className="settings__header-item">
47 {intl.formatMessage(messages.headline)}
48 </span>
49 </div>
50 <div className="settings__body">
51 <h1>{intl.formatMessage(messages.title)}</h1>
52 <Button
53 label={intl.formatMessage(messages.github)}
54 className="franz-form__button--inverted franz-form__button--large"
55 onClick={() => openLink('https://github.com/getferdi/ferdi')}
56 />
57 <Button
58 label={intl.formatMessage(messages.share)}
59 className="franz-form__button--inverted franz-form__button--large"
60 onClick={() => openLink('https://twitter.com/intent/tweet?text=Ferdi%3A%20A%20messaging%20browser%20that%20allows%20you%20to%20combine%20your%20favourite%20messaging%20services%20into%20one%20application.%0A%0ACheck%20out%20Ferdi%20at%20https%3A//getferdi.com')}
61 />
62 <Button
63 label={intl.formatMessage(messages.openCollective)}
64 className="franz-form__button--inverted franz-form__button--large"
65 onClick={() => openLink('https://opencollective.com/getferdi')}
66 />
67 </div>
68 </div>
69 );
70 }
71}
72
73export default SupportFerdiDashboard;
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 366b0113a..2bf46b48d 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -20,7 +20,7 @@ const messages = defineMessages({
20 }, 20 },
21 contentHeadline: { 21 contentHeadline: {
22 id: 'settings.team.contentHeadline', 22 id: 'settings.team.contentHeadline',
23 defaultMessage: '!!!Franz for Teams', 23 defaultMessage: '!!!Ferdi for Teams',
24 }, 24 },
25 intro: { 25 intro: {
26 id: 'settings.team.intro', 26 id: 'settings.team.intro',
@@ -28,7 +28,7 @@ const messages = defineMessages({
28 }, 28 },
29 copy: { 29 copy: {
30 id: 'settings.team.copy', 30 id: 'settings.team.copy',
31 defaultMessage: '!!!Franz for Teams gives you the option to invite co-workers to your team by sending them email invitations and manage their subscriptions in your account’s preferences. Don’t waste time setting up subscriptions for every team member individually, forget about multiple invoices and different billing cycles - one team to rule them all!', 31 defaultMessage: '!!!Ferdi for Teams gives you the option to invite co-workers to your team by sending them email invitations and manage their subscriptions in your account’s preferences. Don’t waste time setting up subscriptions for every team member individually, forget about multiple invoices and different billing cycles - one team to rule them all!',
32 }, 32 },
33 manageButton: { 33 manageButton: {
34 id: 'settings.team.manageAction', 34 id: 'settings.team.manageAction',
diff --git a/src/components/ui/ActivateTrialButton/index.js b/src/components/ui/ActivateTrialButton/index.js
index e0637da90..340123c2f 100644
--- a/src/components/ui/ActivateTrialButton/index.js
+++ b/src/components/ui/ActivateTrialButton/index.js
@@ -5,7 +5,6 @@ import { defineMessages, intlShape } from 'react-intl';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
7import { Button } from '@meetfranz/forms'; 7import { Button } from '@meetfranz/forms';
8import { gaEvent } from '../../../lib/analytics';
9 8
10import UserStore from '../../../stores/UserStore'; 9import UserStore from '../../../stores/UserStore';
11 10
@@ -63,25 +62,9 @@ class ActivateTrialButton extends Component {
63 }; 62 };
64 63
65 handleCTAClick() { 64 handleCTAClick() {
66 const { actions, stores, gaEventInfo } = this.props; 65 const { actions } = this.props;
67 const { hadSubscription } = stores.user.data;
68 // const { defaultTrialPlan } = stores.features.features;
69
70 let label = '';
71 if (!hadSubscription) {
72 // actions.user.activateTrial({ planId: defaultTrialPlan });
73
74 label = 'Start Trial';
75 } else {
76 label = 'Upgrade Account';
77 }
78 66
79 actions.ui.openSettings({ path: 'user' }); 67 actions.ui.openSettings({ path: 'user' });
80
81 if (gaEventInfo) {
82 const { category, event } = gaEventInfo;
83 gaEvent(category, event, label);
84 }
85 } 68 }
86 69
87 render() { 70 render() {
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js
index b0c7fed7b..1fd247d17 100644
--- a/src/components/ui/AppLoader/index.js
+++ b/src/components/ui/AppLoader/index.js
@@ -9,14 +9,13 @@ import { shuffleArray } from '../../../helpers/array-helpers';
9import styles from './styles'; 9import styles from './styles';
10 10
11const textList = shuffleArray([ 11const textList = shuffleArray([
12 'Looking for Sisi', 12 'Adding free features',
13 'Contacting the herald', 13 'Making application usable',
14 'Saddling the unicorn', 14 'Removing unproductive paywalls',
15 'Learning the Waltz', 15 'Creating custom server software',
16 'Visiting Horst & Grete', 16 'Increasing productivity',
17 'Twisting my moustache', 17 'Listening to our userbase',
18 'Playing the trumpet', 18 'Fixing bugs',
19 'Traveling through space & time',
20]); 19]);
21 20
22export default @injectSheet(styles) @withTheme class AppLoader extends Component { 21export default @injectSheet(styles) @withTheme class AppLoader extends Component {
@@ -49,7 +48,7 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component
49 48
50 return ( 49 return (
51 <FullscreenLoader 50 <FullscreenLoader
52 title="Franz" 51 title="Ferdi"
53 className={classes.component} 52 className={classes.component}
54 spinnerColor={theme.colorAppLoaderSpinner} 53 spinnerColor={theme.colorAppLoaderSpinner}
55 > 54 >
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js
index ffc7f7051..e2d7cea83 100644
--- a/src/components/ui/Button.js
+++ b/src/components/ui/Button.js
@@ -69,7 +69,7 @@ export default @observer class Button extends Component {
69 loaded={loaded} 69 loaded={loaded}
70 lines={10} 70 lines={10}
71 scale={0.4} 71 scale={0.4}
72 color={buttonType !== 'secondary' ? '#FFF' : '#373a3c'} 72 color={buttonType !== 'secondary' ? '#FFF' : '#7367F0'}
73 component="span" 73 component="span"
74 /> 74 />
75 {label} 75 {label}
diff --git a/src/components/ui/FullscreenLoader/styles.js b/src/components/ui/FullscreenLoader/styles.js
index 64d24e4ce..d516781a8 100644
--- a/src/components/ui/FullscreenLoader/styles.js
+++ b/src/components/ui/FullscreenLoader/styles.js
@@ -4,6 +4,7 @@ export default {
4 alignItems: 'center', 4 alignItems: 'center',
5 position: 'absolute', 5 position: 'absolute',
6 width: '100%', 6 width: '100%',
7 background: 'linear-gradient( 135deg, #CE9FFC 10%, #7367F0 100%)',
7 }, 8 },
8 component: { 9 component: {
9 width: '100%', 10 width: '100%',
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
index 9b070c4df..4e3eb4ab8 100644
--- a/src/components/ui/Input.js
+++ b/src/components/ui/Input.js
@@ -68,7 +68,7 @@ export default @observer class Input extends Component {
68 68
69 const { passwordScore } = this.state; 69 const { passwordScore } = this.state;
70 70
71 let type = field.type; 71 let { type } = field;
72 if (type === 'password' && this.state.showPassword) { 72 if (type === 'password' && this.state.showPassword) {
73 type = 'text'; 73 type = 'text';
74 } 74 }
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js
index b88686d5e..5f729844b 100644
--- a/src/components/ui/Link.js
+++ b/src/components/ui/Link.js
@@ -25,6 +25,7 @@ export default @inject('stores') @observer class Link extends Component {
25 className, 25 className,
26 activeClassName, 26 activeClassName,
27 strictFilter, 27 strictFilter,
28 style,
28 } = this.props; 29 } = this.props;
29 const { router } = stores; 30 const { router } = stores;
30 31
@@ -44,6 +45,7 @@ export default @inject('stores') @observer class Link extends Component {
44 <a 45 <a
45 href={router.history.createHref(to)} 46 href={router.history.createHref(to)}
46 className={linkClasses} 47 className={linkClasses}
48 style={style}
47 onClick={e => this.onClick(e)} 49 onClick={e => this.onClick(e)}
48 > 50 >
49 {children} 51 {children}
@@ -65,6 +67,7 @@ Link.wrappedComponent.propTypes = {
65 activeClassName: PropTypes.string, 67 activeClassName: PropTypes.string,
66 strictFilter: PropTypes.bool, 68 strictFilter: PropTypes.bool,
67 target: PropTypes.string, 69 target: PropTypes.string,
70 style: PropTypes.object,
68}; 71};
69 72
70Link.wrappedComponent.defaultProps = { 73Link.wrappedComponent.defaultProps = {
@@ -72,4 +75,5 @@ Link.wrappedComponent.defaultProps = {
72 activeClassName: '', 75 activeClassName: '',
73 strictFilter: false, 76 strictFilter: false,
74 target: '', 77 target: '',
78 style: {},
75}; 79};
diff --git a/src/components/ui/Loader.js b/src/components/ui/Loader.js
index f73296bb6..de8769b6c 100644
--- a/src/components/ui/Loader.js
+++ b/src/components/ui/Loader.js
@@ -16,7 +16,7 @@ export default class LoaderComponent extends Component {
16 children: null, 16 children: null,
17 loaded: false, 17 loaded: false,
18 className: '', 18 className: '',
19 color: '#373a3c', 19 color: '#7367F0',
20 }; 20 };
21 21
22 render() { 22 render() {
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index 63d858c47..0af521452 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -41,6 +41,8 @@ export default @injectCSS(styles) class Modal extends Component {
41 showClose, 41 showClose,
42 } = this.props; 42 } = this.props;
43 43
44 const appRoot = document.getElementById('root');
45
44 return ( 46 return (
45 <ReactModal 47 <ReactModal
46 isOpen={isOpen} 48 isOpen={isOpen}
@@ -53,6 +55,7 @@ export default @injectCSS(styles) class Modal extends Component {
53 portal={portal} 55 portal={portal}
54 onRequestClose={close} 56 onRequestClose={close}
55 shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} 57 shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
58 appElement={appRoot}
56 > 59 >
57 {showClose && close && ( 60 {showClose && close && (
58 <button 61 <button
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 8d2746e22..7ba353be3 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -9,8 +9,7 @@ import { oneOrManyChildElements } from '../../../prop-types';
9import UserStore from '../../../stores/UserStore'; 9import UserStore from '../../../stores/UserStore';
10 10
11import styles from './styles'; 11import styles from './styles';
12import { gaEvent } from '../../../lib/analytics'; 12import FeatureStore from '../../../stores/FeaturesStore';
13import { FeatureStore } from '../../../features/utils/FeatureStore';
14 13
15const messages = defineMessages({ 14const messages = defineMessages({
16 action: { 15 action: {
@@ -50,7 +49,6 @@ class PremiumFeatureContainer extends Component {
50 actions, 49 actions,
51 condition, 50 condition,
52 stores, 51 stores,
53 gaEventInfo,
54 } = this.props; 52 } = this.props;
55 53
56 const { intl } = this.context; 54 const { intl } = this.context;
@@ -75,10 +73,6 @@ class PremiumFeatureContainer extends Component {
75 type="button" 73 type="button"
76 onClick={() => { 74 onClick={() => {
77 actions.ui.openSettings({ path: 'user' }); 75 actions.ui.openSettings({ path: 'user' });
78 if (gaEventInfo) {
79 const { category, event, label } = gaEventInfo;
80 gaEvent(category, event, label);
81 }
82 }} 76 }}
83 > 77 >
84 {intl.formatMessage(messages.action)} 78 {intl.formatMessage(messages.action)}
diff --git a/src/components/ui/UpgradeButton/index.js b/src/components/ui/UpgradeButton/index.js
index 73762f0bf..1b764bd90 100644
--- a/src/components/ui/UpgradeButton/index.js
+++ b/src/components/ui/UpgradeButton/index.js
@@ -4,7 +4,6 @@ import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5 5
6import { Button } from '@meetfranz/forms'; 6import { Button } from '@meetfranz/forms';
7import { gaEvent } from '../../../lib/analytics';
8 7
9import UserStore from '../../../stores/UserStore'; 8import UserStore from '../../../stores/UserStore';
10import ActivateTrialButton from '../ActivateTrialButton'; 9import ActivateTrialButton from '../ActivateTrialButton';
@@ -41,13 +40,9 @@ class UpgradeButton extends Component {
41 }; 40 };
42 41
43 handleCTAClick() { 42 handleCTAClick() {
44 const { actions, gaEventInfo } = this.props; 43 const { actions } = this.props;
45 44
46 actions.ui.openSettings({ path: 'user' }); 45 actions.ui.openSettings({ path: 'user' });
47 if (gaEventInfo) {
48 const { category, event } = gaEventInfo;
49 gaEvent(category, event, 'Upgrade Account');
50 }
51 } 46 }
52 47
53 render() { 48 render() {