aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/settings')
-rw-r--r--src/components/settings/account/AccountDashboard.js2
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js63
-rw-r--r--src/components/settings/services/EditServiceForm.js57
-rw-r--r--src/components/settings/settings/EditSettingsForm.js260
-rw-r--r--src/components/settings/supportFerdi/SupportFerdiDashboard.js73
-rw-r--r--src/components/settings/team/TeamDashboard.js153
6 files changed, 515 insertions, 93 deletions
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index b4ff072ab..83dc34a52 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..192cfde7a 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -3,10 +3,13 @@ import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5import { ProBadge } from '@meetfranz/ui'; 5import { ProBadge } from '@meetfranz/ui';
6import { RouterStore } from 'mobx-react-router';
6 7
8import { LOCAL_SERVER, LIVE_API } from '../../../config';
7import Link from '../../ui/Link'; 9import Link from '../../ui/Link';
8import { workspaceStore } from '../../../features/workspaces'; 10import { workspaceStore } from '../../../features/workspaces';
9import UIStore from '../../../stores/UIStore'; 11import UIStore from '../../../stores/UIStore';
12import SettingsStore from '../../../stores/SettingsStore';
10import UserStore from '../../../stores/UserStore'; 13import UserStore from '../../../stores/UserStore';
11import { serviceLimitStore } from '../../../features/serviceLimit'; 14import { serviceLimitStore } from '../../../features/serviceLimit';
12 15
@@ -35,9 +38,9 @@ const messages = defineMessages({
35 id: 'settings.navigation.settings', 38 id: 'settings.navigation.settings',
36 defaultMessage: '!!!Settings', 39 defaultMessage: '!!!Settings',
37 }, 40 },
38 inviteFriends: { 41 supportFerdi: {
39 id: 'settings.navigation.inviteFriends', 42 id: 'settings.navigation.supportFerdi',
40 defaultMessage: '!!!Invite Friends', 43 defaultMessage: '!!!Support Ferdi',
41 }, 44 },
42 logout: { 45 logout: {
43 id: 'settings.navigation.logout', 46 id: 'settings.navigation.logout',
@@ -45,11 +48,18 @@ const messages = defineMessages({
45 }, 48 },
46}); 49});
47 50
48export default @inject('stores') @observer class SettingsNavigation extends Component { 51export default @inject('stores', 'actions') @observer class SettingsNavigation extends Component {
49 static propTypes = { 52 static propTypes = {
50 stores: PropTypes.shape({ 53 stores: PropTypes.shape({
51 ui: PropTypes.instanceOf(UIStore).isRequired, 54 ui: PropTypes.instanceOf(UIStore).isRequired,
52 user: PropTypes.instanceOf(UserStore).isRequired, 55 user: PropTypes.instanceOf(UserStore).isRequired,
56 settings: PropTypes.instanceOf(SettingsStore).isRequired,
57 router: PropTypes.instanceOf(RouterStore).isRequired,
58 }).isRequired,
59 actions: PropTypes.shape({
60 settings: PropTypes.shape({
61 update: PropTypes.func.isRequired,
62 }).isRequired,
53 }).isRequired, 63 }).isRequired,
54 serviceCount: PropTypes.number.isRequired, 64 serviceCount: PropTypes.number.isRequired,
55 workspaceCount: PropTypes.number.isRequired, 65 workspaceCount: PropTypes.number.isRequired,
@@ -59,11 +69,42 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
59 intl: intlShape, 69 intl: intlShape,
60 }; 70 };
61 71
72 handleLoginLogout() {
73 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
74 const isUsingWithoutAccount = this.props.stores.settings.app.server === LOCAL_SERVER;
75
76 if (isLoggedIn) {
77 // Remove current auth token
78 localStorage.removeItem('authToken');
79
80 if (isUsingWithoutAccount) {
81 // Reset server back to Ferdi API
82 this.props.actions.settings.update({
83 type: 'app',
84 data: {
85 server: LIVE_API,
86 },
87 });
88 }
89 this.props.stores.user.isLoggingOut = true;
90 }
91
92 this.props.stores.router.push(isLoggedIn ? '/auth/logout' : '/auth/welcome');
93
94 if (isLoggedIn) {
95 // Reload Ferdi, otherwise many settings won't sync correctly with the server
96 // after logging into another account
97 window.location.reload();
98 }
99 }
100
62 render() { 101 render() {
63 const { serviceCount, workspaceCount, stores } = this.props; 102 const { serviceCount, workspaceCount, stores } = this.props;
64 const { isDarkThemeActive } = stores.ui; 103 const { isDarkThemeActive } = stores.ui;
65 const { router, user } = stores; 104 const { router, user } = stores;
66 const { intl } = this.context; 105 const { intl } = this.context;
106 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
107 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER;
67 108
68 return ( 109 return (
69 <div className="settings-navigation"> 110 <div className="settings-navigation">
@@ -128,19 +169,21 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
128 {intl.formatMessage(messages.settings)} 169 {intl.formatMessage(messages.settings)}
129 </Link> 170 </Link>
130 <Link 171 <Link
131 to="/settings/invite" 172 to="/settings/support"
132 className="settings-navigation__link" 173 className="settings-navigation__link"
133 activeClassName="is-active" 174 activeClassName="is-active"
134 > 175 >
135 {intl.formatMessage(messages.inviteFriends)} 176 {intl.formatMessage(messages.supportFerdi)}
136 </Link> 177 </Link>
137 <span className="settings-navigation__expander" /> 178 <span className="settings-navigation__expander" />
138 <Link 179 <button
139 to="/auth/logout" 180 type="button"
181 to={isLoggedIn ? '/auth/logout' : '/auth/welcome'}
140 className="settings-navigation__link" 182 className="settings-navigation__link"
183 onClick={this.handleLoginLogout.bind(this)}
141 > 184 >
142 {intl.formatMessage(messages.logout)} 185 { isLoggedIn && !isUsingWithoutAccount ? intl.formatMessage(messages.logout) : 'Login'}
143 </Link> 186 </button>
144 </div> 187 </div>
145 ); 188 );
146 } 189 }
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 5cde0db8e..fa34ac60b 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -29,6 +29,10 @@ const messages = defineMessages({
29 id: 'settings.service.form.deleteButton', 29 id: 'settings.service.form.deleteButton',
30 defaultMessage: '!!!Delete Service', 30 defaultMessage: '!!!Delete Service',
31 }, 31 },
32 openDarkmodeCss: {
33 id: 'settings.service.form.openDarkmodeCss',
34 defaultMessage: '!!!Open darkmode.css',
35 },
32 availableServices: { 36 availableServices: {
33 id: 'settings.service.form.availableServices', 37 id: 'settings.service.form.availableServices',
34 defaultMessage: '!!!Available services', 38 defaultMessage: '!!!Available services',
@@ -63,7 +67,7 @@ const messages = defineMessages({
63 }, 67 },
64 customUrlPremiumInfo: { 68 customUrlPremiumInfo: {
65 id: 'settings.service.form.customUrlPremiumInfo', 69 id: 'settings.service.form.customUrlPremiumInfo',
66 defaultMessage: '!!!To add self hosted services, you need a Franz Premium Supporter Account.', 70 defaultMessage: '!!!To add self hosted services, you need a Ferdi Premium Supporter Account.',
67 }, 71 },
68 customUrlUpgradeAccount: { 72 customUrlUpgradeAccount: {
69 id: 'settings.service.form.customUrlUpgradeAccount', 73 id: 'settings.service.form.customUrlUpgradeAccount',
@@ -103,11 +107,11 @@ const messages = defineMessages({
103 }, 107 },
104 proxyRestartInfo: { 108 proxyRestartInfo: {
105 id: 'settings.service.form.proxy.restartInfo', 109 id: 'settings.service.form.proxy.restartInfo',
106 defaultMessage: '!!!Please restart Franz after changing proxy Settings.', 110 defaultMessage: '!!!Please restart Ferdi after changing proxy Settings.',
107 }, 111 },
108 proxyInfo: { 112 proxyInfo: {
109 id: 'settings.service.form.proxy.info', 113 id: 'settings.service.form.proxy.info',
110 defaultMessage: '!!!Proxy settings will not be synchronized with the Franz servers.', 114 defaultMessage: '!!!Proxy settings will not be synchronized with the Ferdi servers.',
111 }, 115 },
112}); 116});
113 117
@@ -127,6 +131,8 @@ export default @observer class EditServiceForm extends Component {
127 form: PropTypes.instanceOf(Form).isRequired, 131 form: PropTypes.instanceOf(Form).isRequired,
128 onSubmit: PropTypes.func.isRequired, 132 onSubmit: PropTypes.func.isRequired,
129 onDelete: PropTypes.func.isRequired, 133 onDelete: PropTypes.func.isRequired,
134 openDarkmodeCss: PropTypes.func.isRequired,
135 isOpeningDarkModeCss: PropTypes.bool.isRequired,
130 isSaving: PropTypes.bool.isRequired, 136 isSaving: PropTypes.bool.isRequired,
131 isDeleting: PropTypes.bool.isRequired, 137 isDeleting: PropTypes.bool.isRequired,
132 isProxyFeatureEnabled: PropTypes.bool.isRequired, 138 isProxyFeatureEnabled: PropTypes.bool.isRequired,
@@ -155,7 +161,7 @@ export default @observer class EditServiceForm extends Component {
155 const values = form.values(); 161 const values = form.values();
156 let isValid = true; 162 let isValid = true;
157 163
158 const files = form.$('customIcon').files; 164 const { files } = form.$('customIcon');
159 if (files) { 165 if (files) {
160 values.iconFile = files[0]; 166 values.iconFile = files[0];
161 } 167 }
@@ -193,6 +199,8 @@ export default @observer class EditServiceForm extends Component {
193 isSaving, 199 isSaving,
194 isDeleting, 200 isDeleting,
195 onDelete, 201 onDelete,
202 openDarkmodeCss,
203 isOpeningDarkModeCss,
196 isProxyFeatureEnabled, 204 isProxyFeatureEnabled,
197 isServiceProxyIncludedInCurrentPlan, 205 isServiceProxyIncludedInCurrentPlan,
198 isSpellcheckerIncludedInCurrentPlan, 206 isSpellcheckerIncludedInCurrentPlan,
@@ -218,6 +226,23 @@ export default @observer class EditServiceForm extends Component {
218 /> 226 />
219 ); 227 );
220 228
229 const openDarkmodeCssButton = isOpeningDarkModeCss ? (
230 <Button
231 label={intl.formatMessage(messages.openDarkmodeCss)}
232 loaded={false}
233 buttonType="secondary"
234 className="settings__open-dark-mode-button"
235 disabled
236 />
237 ) : (
238 <Button
239 buttonType="secondary"
240 label={intl.formatMessage(messages.openDarkmodeCss)}
241 className="settings__open-dark-mode-button"
242 onClick={openDarkmodeCss}
243 />
244 );
245
221 let activeTabIndex = 0; 246 let activeTabIndex = 0;
222 if (recipe.hasHostedOption && service.team) { 247 if (recipe.hasHostedOption && service.team) {
223 activeTabIndex = 1; 248 activeTabIndex = 1;
@@ -303,6 +328,18 @@ export default @observer class EditServiceForm extends Component {
303 )} 328 )}
304 </Tabs> 329 </Tabs>
305 )} 330 )}
331
332 {recipe.message && (
333 <p
334 className="settings__message"
335 style={{
336 marginTop: 0,
337 }}
338 >
339 <span className="mdi mdi-information" />
340 {recipe.message}
341 </p>
342 )}
306 <div className="service-flex-grid"> 343 <div className="service-flex-grid">
307 <div className="settings__options"> 344 <div className="settings__options">
308 <div className="settings__settings-group"> 345 <div className="settings__settings-group">
@@ -329,9 +366,7 @@ export default @observer class EditServiceForm extends Component {
329 366
330 <div className="settings__settings-group"> 367 <div className="settings__settings-group">
331 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> 368 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3>
332 {recipe.hasDarkMode && ( 369 <Toggle field={form.$('isDarkModeEnabled')} />
333 <Toggle field={form.$('isDarkModeEnabled')} />
334 )}
335 <Toggle field={form.$('isEnabled')} /> 370 <Toggle field={form.$('isEnabled')} />
336 </div> 371 </div>
337 </div> 372 </div>
@@ -394,18 +429,12 @@ export default @observer class EditServiceForm extends Component {
394 </div> 429 </div>
395 </PremiumFeatureContainer> 430 </PremiumFeatureContainer>
396 )} 431 )}
397
398 {recipe.message && (
399 <p className="settings__message">
400 <span className="mdi mdi-information" />
401 {recipe.message}
402 </p>
403 )}
404 </form> 432 </form>
405 </div> 433 </div>
406 <div className="settings__controls"> 434 <div className="settings__controls">
407 {/* Delete Button */} 435 {/* Delete Button */}
408 {action === 'edit' && deleteButton} 436 {action === 'edit' && deleteButton}
437 {action === 'edit' && openDarkmodeCssButton}
409 438
410 {/* Save Button */} 439 {/* Save Button */}
411 {isSaving || isValidatingCustomUrl ? ( 440 {isSaving || isValidatingCustomUrl ? (
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 0b69f7514..2be5c4ed7 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',
@@ -33,13 +79,21 @@ const messages = defineMessages({
33 id: 'settings.app.headlineAppearance', 79 id: 'settings.app.headlineAppearance',
34 defaultMessage: '!!!Appearance', 80 defaultMessage: '!!!Appearance',
35 }, 81 },
82 universalDarkModeInfo: {
83 id: 'settings.app.universalDarkModeInfo',
84 defaultMessage: '!!!Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.',
85 },
86 accentColorInfo: {
87 id: 'settings.app.accentColorInfo',
88 defaultMessage: '!!!Write your accent color in a CSS-compatible format. (Default: #7367f0)',
89 },
36 headlineAdvanced: { 90 headlineAdvanced: {
37 id: 'settings.app.headlineAdvanced', 91 id: 'settings.app.headlineAdvanced',
38 defaultMessage: '!!!Advanced', 92 defaultMessage: '!!!Advanced',
39 }, 93 },
40 translationHelp: { 94 translationHelp: {
41 id: 'settings.app.translationHelp', 95 id: 'settings.app.translationHelp',
42 defaultMessage: '!!!Help us to translate Franz into your language.', 96 defaultMessage: '!!!Help us to translate Ferdi into your language.',
43 }, 97 },
44 subheadlineCache: { 98 subheadlineCache: {
45 id: 'settings.app.subheadlineCache', 99 id: 'settings.app.subheadlineCache',
@@ -47,7 +101,7 @@ const messages = defineMessages({
47 }, 101 },
48 cacheInfo: { 102 cacheInfo: {
49 id: 'settings.app.cacheInfo', 103 id: 'settings.app.cacheInfo',
50 defaultMessage: '!!!Franz cache is currently using {size} of disk space.', 104 defaultMessage: '!!!Ferdi cache is currently using {size} of disk space.',
51 }, 105 },
52 buttonClearAllCache: { 106 buttonClearAllCache: {
53 id: 'settings.app.buttonClearAllCache', 107 id: 'settings.app.buttonClearAllCache',
@@ -71,7 +125,7 @@ const messages = defineMessages({
71 }, 125 },
72 updateStatusUpToDate: { 126 updateStatusUpToDate: {
73 id: 'settings.app.updateStatusUpToDate', 127 id: 'settings.app.updateStatusUpToDate',
74 defaultMessage: '!!!You are using the latest version of Franz', 128 defaultMessage: '!!!You are using the latest version of Ferdi',
75 }, 129 },
76 currentVersion: { 130 currentVersion: {
77 id: 'settings.app.currentVersion', 131 id: 'settings.app.currentVersion',
@@ -103,6 +157,11 @@ export default @observer class EditSettingsForm extends Component {
103 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, 157 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
104 isTodosEnabled: PropTypes.bool.isRequired, 158 isTodosEnabled: PropTypes.bool.isRequired,
105 isWorkspaceEnabled: PropTypes.bool.isRequired, 159 isWorkspaceEnabled: PropTypes.bool.isRequired,
160 server: PropTypes.string.isRequired,
161 noUpdates: PropTypes.bool.isRequired,
162 hibernationEnabled: PropTypes.bool.isRequired,
163 isDarkmodeEnabled: PropTypes.bool.isRequired,
164 openProcessManager: PropTypes.func.isRequired,
106 }; 165 };
107 166
108 static contextTypes = { 167 static contextTypes = {
@@ -135,6 +194,11 @@ export default @observer class EditSettingsForm extends Component {
135 isSpellcheckerIncludedInCurrentPlan, 194 isSpellcheckerIncludedInCurrentPlan,
136 isTodosEnabled, 195 isTodosEnabled,
137 isWorkspaceEnabled, 196 isWorkspaceEnabled,
197 server,
198 noUpdates,
199 hibernationEnabled,
200 isDarkmodeEnabled,
201 openProcessManager,
138 } = this.props; 202 } = this.props;
139 const { intl } = this.context; 203 const { intl } = this.context;
140 204
@@ -147,6 +211,13 @@ export default @observer class EditSettingsForm extends Component {
147 updateButtonLabelMessage = messages.buttonSearchForUpdate; 211 updateButtonLabelMessage = messages.buttonSearchForUpdate;
148 } 212 }
149 213
214 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
215
216 const {
217 lockingFeatureEnabled,
218 scheduledDNDEnabled,
219 } = window.ferdi.stores.settings.all.app;
220
150 return ( 221 return (
151 <div className="settings__main"> 222 <div className="settings__main">
152 <div className="settings__header"> 223 <div className="settings__header">
@@ -163,21 +234,177 @@ export default @observer class EditSettingsForm extends Component {
163 <Toggle field={form.$('autoLaunchOnStart')} /> 234 <Toggle field={form.$('autoLaunchOnStart')} />
164 <Toggle field={form.$('runInBackground')} /> 235 <Toggle field={form.$('runInBackground')} />
165 <Toggle field={form.$('enableSystemTray')} /> 236 <Toggle field={form.$('enableSystemTray')} />
237 <Toggle field={form.$('privateNotifications')} />
238 <Toggle field={form.$('showServiceNavigationBar')} />
239 <Toggle field={form.$('hibernate')} />
240 {hibernationEnabled && (
241 <Select field={form.$('hibernationStrategy')} />
242 )}
243 <p
244 className="settings__message"
245 style={{
246 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
247 }}
248 >
249 <span>
250 { intl.formatMessage(messages.hibernateInfo) }
251 </span>
252 </p>
166 {process.platform === 'win32' && ( 253 {process.platform === 'win32' && (
167 <Toggle field={form.$('minimizeToSystemTray')} /> 254 <Toggle field={form.$('minimizeToSystemTray')} />
168 )} 255 )}
256 <Input
257 placeholder="Server"
258 onChange={e => this.submit(e)}
259 field={form.$('server')}
260 autoFocus
261 />
262 {isLoggedIn && (
263 <p>{ intl.formatMessage(messages.serverInfo) }</p>
264 )}
265 {server === 'https://api.franzinfra.com' && (
266 <p
267 className="settings__message"
268 style={{
269 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
270 }}
271 >
272 <span
273 dangerouslySetInnerHTML={{
274 __html:
275 // Needed to make links work
276 escapeHtml(
277 intl.formatMessage(messages.serverMoneyInfo),
278 ).replace('[Link 1]', '<a href="https://www.meetfranz.com/pricing" target="_blank">')
279 .replace('[Link 2]', '<a href="https://github.com/getferdi/server" target="_blank">')
280 .replace(/\[\/Link]/g, '</a>'),
281 }}
282 style={{
283 whiteSpace: 'pre-wrap',
284 }}
285 />
286 </p>
287 )}
169 {isWorkspaceEnabled && ( 288 {isWorkspaceEnabled && (
170 <Toggle field={form.$('keepAllWorkspacesLoaded')} /> 289 <Toggle field={form.$('keepAllWorkspacesLoaded')} />
171 )} 290 )}
172 {isTodosEnabled && ( 291 {isTodosEnabled && (
173 <Toggle field={form.$('enableTodos')} /> 292 <>
293 <Toggle field={form.$('enableTodos')} />
294 <Input
295 placeholder="Todo Server"
296 onChange={e => this.submit(e)}
297 field={form.$('todoServer')}
298 />
299 <p>{ intl.formatMessage(messages.todoServerInfo) }</p>
300 </>
174 )} 301 )}
175 302
303 <Toggle field={form.$('lockingFeatureEnabled')} />
304 {lockingFeatureEnabled && (
305 <>
306 <Input
307 placeholder={intl.formatMessage(messages.lockedPassword)}
308 onChange={e => this.submit(e)}
309 field={form.$('lockedPassword')}
310 type="password"
311 scorePassword
312 showPasswordToggle
313 />
314 <p>
315 { intl.formatMessage(messages.lockedPasswordInfo) }
316 </p>
317 </>
318 )}
319 <p
320 className="settings__message"
321 style={{
322 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
323 }}
324 >
325 <span>
326 { intl.formatMessage(messages.lockInfo) }
327 </span>
328 </p>
329
330
331 <Toggle field={form.$('scheduledDNDEnabled')} />
332 {scheduledDNDEnabled && (
333 <>
334 <div style={{
335 display: 'flex',
336 justifyContent: 'center',
337 }}
338 >
339 <div style={{
340 padding: '0 1rem',
341 width: '100%',
342 }}
343 >
344 <Input
345 placeholder="17:00"
346 onChange={e => this.submit(e)}
347 field={form.$('scheduledDNDStart')}
348 type="time"
349 />
350 </div>
351 <div style={{
352 padding: '0 1rem',
353 width: '100%',
354 }}
355 >
356 <Input
357 placeholder="09:00"
358 onChange={e => this.submit(e)}
359 field={form.$('scheduledDNDEnd')}
360 type="time"
361 />
362 </div>
363 </div>
364 <p>
365 { intl.formatMessage(messages.scheduledDNDTimeInfo) }
366 </p>
367 </>
368 )}
369 <p
370 className="settings__message"
371 style={{
372 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
373 }}
374 >
375 <span>
376 { intl.formatMessage(messages.scheduledDNDInfo) }
377 </span>
378 </p>
379
380
176 {/* Appearance */} 381 {/* Appearance */}
177 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2> 382 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2>
178 <Toggle field={form.$('showDisabledServices')} /> 383 <Toggle field={form.$('showDisabledServices')} />
179 <Toggle field={form.$('showMessageBadgeWhenMuted')} /> 384 <Toggle field={form.$('showMessageBadgeWhenMuted')} />
180 <Toggle field={form.$('darkMode')} /> 385 <Toggle field={form.$('darkMode')} />
386 {isDarkmodeEnabled && (
387 <>
388 <Toggle field={form.$('universalDarkMode')} />
389 <p
390 className="settings__message"
391 style={{
392 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
393 }}
394 >
395 <span>
396 { intl.formatMessage(messages.universalDarkModeInfo) }
397 </span>
398 </p>
399 </>
400 )}
401
402 <Input
403 placeholder="Accent Color"
404 onChange={e => this.submit(e)}
405 field={form.$('accentColor')}
406 />
407 <p>{intl.formatMessage(messages.accentColorInfo)}</p>
181 408
182 {/* Language */} 409 {/* Language */}
183 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> 410 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2>
@@ -227,6 +454,16 @@ export default @observer class EditSettingsForm extends Component {
227 loaded={!isClearingAllCache} 454 loaded={!isClearingAllCache}
228 /> 455 />
229 </p> 456 </p>
457 <div style={{
458 marginTop: 20,
459 }}
460 >
461 <Button
462 buttonType="secondary"
463 label="Open Process Manager"
464 onClick={openProcessManager}
465 />
466 </div>
230 </div> 467 </div>
231 468
232 {/* Updates */} 469 {/* Updates */}
@@ -241,7 +478,7 @@ export default @observer class EditSettingsForm extends Component {
241 buttonType="secondary" 478 buttonType="secondary"
242 label={intl.formatMessage(updateButtonLabelMessage)} 479 label={intl.formatMessage(updateButtonLabelMessage)}
243 onClick={checkForUpdates} 480 onClick={checkForUpdates}
244 disabled={isCheckingForUpdates || isUpdateAvailable} 481 disabled={noUpdates || isCheckingForUpdates || isUpdateAvailable}
245 loaded={!isCheckingForUpdates || !isUpdateAvailable} 482 loaded={!isCheckingForUpdates || !isUpdateAvailable}
246 /> 483 />
247 )} 484 )}
@@ -250,6 +487,7 @@ export default @observer class EditSettingsForm extends Component {
250 )} 487 )}
251 <br /> 488 <br />
252 <Toggle field={form.$('beta')} /> 489 <Toggle field={form.$('beta')} />
490 <Toggle field={form.$('noUpdates')} />
253 {intl.formatMessage(messages.currentVersion)} 491 {intl.formatMessage(messages.currentVersion)}
254 {' '} 492 {' '}
255 {remote.app.getVersion()} 493 {remote.app.getVersion()}
@@ -257,6 +495,18 @@ export default @observer class EditSettingsForm extends Component {
257 <span className="mdi mdi-information" /> 495 <span className="mdi mdi-information" />
258 {intl.formatMessage(messages.languageDisclaimer)} 496 {intl.formatMessage(messages.languageDisclaimer)}
259 </p> 497 </p>
498 <p className="settings__message">
499 <span className="mdi mdi-github-face" />
500 <span>
501 Ferdi is based on
502 {' '}
503 <a href="https://github.com/meetfranz/franz" target="_blank">Franz</a>
504 , a project published
505 under the
506 {' '}
507 <a href="https://github.com/meetfranz/franz/blob/master/LICENSE" target="_blank">Apache-2.0 License</a>
508 </span>
509 </p>
260 </form> 510 </form>
261 </div> 511 </div>
262 </div> 512 </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..7e6d93997 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',
@@ -38,6 +38,14 @@ const messages = defineMessages({
38 id: 'settings.team.upgradeAction', 38 id: 'settings.team.upgradeAction',
39 defaultMessage: '!!!Upgrade your Account', 39 defaultMessage: '!!!Upgrade your Account',
40 }, 40 },
41 teamsUnavailable: {
42 id: 'settings.team.teamsUnavailable',
43 defaultMessage: '!!!Teams are unavailable',
44 },
45 teamsUnavailableInfo: {
46 id: 'settings.team.teamsUnavailableInfo',
47 defaultMessage: '!!!Teams are currently only available when using the Franz Server and after paying for Franz Professional. Please change your server to https://api.franzinfra.com to use teams.',
48 },
41}); 49});
42 50
43const styles = { 51const styles = {
@@ -98,6 +106,7 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
98 openTeamManagement: PropTypes.func.isRequired, 106 openTeamManagement: PropTypes.func.isRequired,
99 classes: PropTypes.object.isRequired, 107 classes: PropTypes.object.isRequired,
100 isProUser: PropTypes.bool.isRequired, 108 isProUser: PropTypes.bool.isRequired,
109 server: PropTypes.string.isRequired,
101 }; 110 };
102 111
103 static contextTypes = { 112 static contextTypes = {
@@ -112,9 +121,84 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
112 openTeamManagement, 121 openTeamManagement,
113 isProUser, 122 isProUser,
114 classes, 123 classes,
124 server,
115 } = this.props; 125 } = this.props;
116 const { intl } = this.context; 126 const { intl } = this.context;
117 127
128 if (server === 'https://api.franzinfra.com') {
129 return (
130 <div className="settings__main">
131 <div className="settings__header">
132 <span className="settings__header-item">
133 {intl.formatMessage(messages.headline)}
134 </span>
135 </div>
136 <div className="settings__body">
137 {isLoading && (
138 <Loader />
139 )}
140
141 {!isLoading && userInfoRequestFailed && (
142 <Infobox
143 icon="alert"
144 type="danger"
145 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)}
146 ctaLoading={isLoading}
147 ctaOnClick={retryUserInfoRequest}
148 >
149 {intl.formatMessage(messages.userInfoRequestFailed)}
150 </Infobox>
151 )}
152
153 {!userInfoRequestFailed && (
154 <>
155 {!isLoading && (
156 <>
157 <>
158 <h1 className={classnames({
159 [classes.headline]: true,
160 [classes.headlineWithSpacing]: isProUser,
161 })}
162 >
163 {intl.formatMessage(messages.contentHeadline)}
164
165 </h1>
166 {!isProUser && (
167 <Badge className={classes.proRequired}>{intl.formatMessage(globalMessages.proRequired)}</Badge>
168 )}
169 <div className={classes.container}>
170 <div className={classes.content}>
171 <p>{intl.formatMessage(messages.intro)}</p>
172 <p>{intl.formatMessage(messages.copy)}</p>
173 </div>
174 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" />
175 </div>
176 <div className={classes.buttonContainer}>
177 {!isProUser ? (
178 <UpgradeButton
179 className={classes.cta}
180 gaEventInfo={{ category: 'Todos', event: 'upgrade' }}
181 requiresPro
182 short
183 />
184 ) : (
185 <Button
186 label={intl.formatMessage(messages.manageButton)}
187 onClick={openTeamManagement}
188 className={classes.cta}
189 />
190 )}
191 </div>
192 </>
193 </>
194 )}
195 </>
196 )}
197 </div>
198 <ReactTooltip place="right" type="dark" effect="solid" />
199 </div>
200 );
201 }
118 return ( 202 return (
119 <div className="settings__main"> 203 <div className="settings__main">
120 <div className="settings__header"> 204 <div className="settings__header">
@@ -123,68 +207,11 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
123 </span> 207 </span>
124 </div> 208 </div>
125 <div className="settings__body"> 209 <div className="settings__body">
126 {isLoading && ( 210 <h1 className={classes.headline}>
127 <Loader /> 211 {intl.formatMessage(messages.teamsUnavailable)}
128 )} 212 </h1>
129 213 {intl.formatMessage(messages.teamsUnavailableInfo)}
130 {!isLoading && userInfoRequestFailed && (
131 <Infobox
132 icon="alert"
133 type="danger"
134 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)}
135 ctaLoading={isLoading}
136 ctaOnClick={retryUserInfoRequest}
137 >
138 {intl.formatMessage(messages.userInfoRequestFailed)}
139 </Infobox>
140 )}
141
142 {!userInfoRequestFailed && (
143 <>
144 {!isLoading && (
145 <>
146 <>
147 <h1 className={classnames({
148 [classes.headline]: true,
149 [classes.headlineWithSpacing]: isProUser,
150 })}
151 >
152 {intl.formatMessage(messages.contentHeadline)}
153
154 </h1>
155 {!isProUser && (
156 <Badge className={classes.proRequired}>{intl.formatMessage(globalMessages.proRequired)}</Badge>
157 )}
158 <div className={classes.container}>
159 <div className={classes.content}>
160 <p>{intl.formatMessage(messages.intro)}</p>
161 <p>{intl.formatMessage(messages.copy)}</p>
162 </div>
163 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" />
164 </div>
165 <div className={classes.buttonContainer}>
166 {!isProUser ? (
167 <UpgradeButton
168 className={classes.cta}
169 gaEventInfo={{ category: 'Todos', event: 'upgrade' }}
170 requiresPro
171 short
172 />
173 ) : (
174 <Button
175 label={intl.formatMessage(messages.manageButton)}
176 onClick={openTeamManagement}
177 className={classes.cta}
178 />
179 )}
180 </div>
181 </>
182 </>
183 )}
184 </>
185 )}
186 </div> 214 </div>
187 <ReactTooltip place="right" type="dark" effect="solid" />
188 </div> 215 </div>
189 ); 216 );
190 } 217 }