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.js159
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js16
-rw-r--r--src/components/settings/recipes/RecipeItem.js5
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js77
-rw-r--r--src/components/settings/services/EditServiceForm.js142
-rw-r--r--src/components/settings/services/ServicesDashboard.js6
-rw-r--r--src/components/settings/settings/EditSettingsForm.js91
-rw-r--r--src/components/settings/supportFerdi/SupportFerdiDashboard.js18
-rw-r--r--src/components/settings/team/TeamDashboard.js39
-rw-r--r--src/components/settings/user/EditUserForm.js2
10 files changed, 158 insertions, 397 deletions
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 68d88e218..ef7748343 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -3,14 +3,11 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { ProBadge, H1, H2 } from '@meetfranz/ui'; 6import { H1, H2 } from '@meetfranz/ui';
7import moment from 'moment';
8 7
9import Loader from '../../ui/Loader'; 8import Loader from '../../ui/Loader';
10import Button from '../../ui/Button'; 9import Button from '../../ui/Button';
11import Infobox from '../../ui/Infobox'; 10import Infobox from '../../ui/Infobox';
12import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
13import { i18nPlanName } from '../../../helpers/plan-helpers';
14import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config'; 11import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config';
15 12
16const messages = defineMessages({ 13const messages = defineMessages({
@@ -18,30 +15,10 @@ const messages = defineMessages({
18 id: 'settings.account.headline', 15 id: 'settings.account.headline',
19 defaultMessage: '!!!Account', 16 defaultMessage: '!!!Account',
20 }, 17 },
21 headlineSubscription: {
22 id: 'settings.account.headlineSubscription',
23 defaultMessage: '!!!Your Subscription',
24 },
25 headlineDangerZone: { 18 headlineDangerZone: {
26 id: 'settings.account.headlineDangerZone', 19 id: 'settings.account.headlineDangerZone',
27 defaultMessage: '!!Danger Zone', 20 defaultMessage: '!!Danger Zone',
28 }, 21 },
29 manageSubscriptionButtonLabel: {
30 id: 'settings.account.manageSubscription.label',
31 defaultMessage: '!!!Manage your subscription',
32 },
33 upgradeAccountToPro: {
34 id: 'settings.account.upgradeToPro.label',
35 defaultMessage: '!!!Upgrade to Franz Professional',
36 },
37 accountTypeBasic: {
38 id: 'settings.account.accountType.basic',
39 defaultMessage: '!!!Basic Account',
40 },
41 accountTypePremium: {
42 id: 'settings.account.accountType.premium',
43 defaultMessage: '!!!Premium Supporter Account',
44 },
45 accountEditButton: { 22 accountEditButton: {
46 id: 'settings.account.account.editButton', 23 id: 'settings.account.account.editButton',
47 defaultMessage: '!!!Edit Account', 24 defaultMessage: '!!!Edit Account',
@@ -50,10 +27,6 @@ const messages = defineMessages({
50 id: 'settings.account.headlineInvoices', 27 id: 'settings.account.headlineInvoices',
51 defaultMessage: '!!Invoices', 28 defaultMessage: '!!Invoices',
52 }, 29 },
53 invoiceDownload: {
54 id: 'settings.account.invoiceDownload',
55 defaultMessage: '!!!Download',
56 },
57 userInfoRequestFailed: { 30 userInfoRequestFailed: {
58 id: 'settings.account.userInfoRequestFailed', 31 id: 'settings.account.userInfoRequestFailed',
59 defaultMessage: '!!!Could not load user information', 32 defaultMessage: '!!!Could not load user information',
@@ -76,23 +49,10 @@ const messages = defineMessages({
76 defaultMessage: 49 defaultMessage:
77 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 50 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
78 }, 51 },
79 trial: {
80 id: 'settings.account.trial',
81 defaultMessage: '!!!Free Trial',
82 },
83 yourLicense: { 52 yourLicense: {
84 id: 'settings.account.yourLicense', 53 id: 'settings.account.yourLicense',
85 defaultMessage: '!!!Your Franz License:', 54 defaultMessage: '!!!Your Franz License:',
86 }, 55 },
87 trialEndsIn: {
88 id: 'settings.account.trialEndsIn',
89 defaultMessage: '!!!Your free trial ends in {duration}.',
90 },
91 trialUpdateBillingInformation: {
92 id: 'settings.account.trialUpdateBillingInfo',
93 defaultMessage:
94 '!!!Please update your billing info to continue using {license} after your trial period.',
95 },
96 accountUnavailable: { 56 accountUnavailable: {
97 id: 'settings.account.accountUnavailable', 57 id: 'settings.account.accountUnavailable',
98 defaultMessage: 'Account is unavailable', 58 defaultMessage: 'Account is unavailable',
@@ -107,8 +67,6 @@ const messages = defineMessages({
107class AccountDashboard extends Component { 67class AccountDashboard extends Component {
108 static propTypes = { 68 static propTypes = {
109 user: MobxPropTypes.observableObject.isRequired, 69 user: MobxPropTypes.observableObject.isRequired,
110 isPremiumOverrideUser: PropTypes.bool.isRequired,
111 isProUser: PropTypes.bool.isRequired,
112 isLoading: PropTypes.bool.isRequired, 70 isLoading: PropTypes.bool.isRequired,
113 userInfoRequestFailed: PropTypes.bool.isRequired, 71 userInfoRequestFailed: PropTypes.bool.isRequired,
114 retryUserInfoRequest: PropTypes.func.isRequired, 72 retryUserInfoRequest: PropTypes.func.isRequired,
@@ -116,10 +74,7 @@ class AccountDashboard extends Component {
116 isLoadingDeleteAccount: PropTypes.bool.isRequired, 74 isLoadingDeleteAccount: PropTypes.bool.isRequired,
117 isDeleteAccountSuccessful: PropTypes.bool.isRequired, 75 isDeleteAccountSuccessful: PropTypes.bool.isRequired,
118 openEditAccount: PropTypes.func.isRequired, 76 openEditAccount: PropTypes.func.isRequired,
119 openBilling: PropTypes.func.isRequired,
120 upgradeToPro: PropTypes.func.isRequired,
121 openInvoices: PropTypes.func.isRequired, 77 openInvoices: PropTypes.func.isRequired,
122 onCloseSubscriptionWindow: PropTypes.func.isRequired,
123 server: PropTypes.string.isRequired, 78 server: PropTypes.string.isRequired,
124 }; 79 };
125 80
@@ -130,8 +85,6 @@ class AccountDashboard extends Component {
130 render() { 85 render() {
131 const { 86 const {
132 user, 87 user,
133 isPremiumOverrideUser,
134 isProUser,
135 isLoading, 88 isLoading,
136 userInfoRequestFailed, 89 userInfoRequestFailed,
137 retryUserInfoRequest, 90 retryUserInfoRequest,
@@ -139,20 +92,11 @@ class AccountDashboard extends Component {
139 isLoadingDeleteAccount, 92 isLoadingDeleteAccount,
140 isDeleteAccountSuccessful, 93 isDeleteAccountSuccessful,
141 openEditAccount, 94 openEditAccount,
142 openBilling,
143 upgradeToPro,
144 openInvoices, 95 openInvoices,
145 onCloseSubscriptionWindow,
146 server, 96 server,
147 } = this.props; 97 } = this.props;
148 const { intl } = this.context; 98 const { intl } = this.context;
149 99
150 let planName = '';
151
152 if (user.team && user.team.plan) {
153 planName = i18nPlanName(user.team.plan, intl);
154 }
155
156 const isUsingWithoutAccount = server === LOCAL_SERVER; 100 const isUsingWithoutAccount = server === LOCAL_SERVER;
157 const isUsingFranzServer = server === LIVE_FRANZ_API; 101 const isUsingFranzServer = server === LIVE_FRANZ_API;
158 102
@@ -210,98 +154,40 @@ class AccountDashboard extends Component {
210 <div className="account__info"> 154 <div className="account__info">
211 <H1> 155 <H1>
212 <span className="username">{`${user.firstname} ${user.lastname}`}</span> 156 <span className="username">{`${user.firstname} ${user.lastname}`}</span>
213 {user.isPremium && (
214 <>
215 {' '}
216 <ProBadge />
217 </>
218 )}
219 </H1> 157 </H1>
220 <p> 158 <p>
221 {user.organization && `${user.organization}, `} 159 {user.organization && `${user.organization}, `}
222 {user.email} 160 {user.email}
223 </p> 161 </p>
224 {user.isPremium && ( 162 <div className="manage-user-links">
225 <div className="manage-user-links"> 163 <Button
226 <Button 164 label={intl.formatMessage(
227 label={intl.formatMessage( 165 messages.accountEditButton,
228 messages.accountEditButton, 166 )}
229 )} 167 className="franz-form__button--inverted"
230 className="franz-form__button--inverted" 168 onClick={openEditAccount}
231 onClick={openEditAccount} 169 />
232 /> 170 </div>
233 </div>
234 )}
235 </div> 171 </div>
236 {!user.isPremium && ( 172 <Button
237 <Button 173 label={intl.formatMessage(
238 label={intl.formatMessage( 174 messages.accountEditButton,
239 messages.accountEditButton, 175 )}
240 )} 176 className="franz-form__button--inverted"
241 className="franz-form__button--inverted" 177 onClick={openEditAccount}
242 onClick={openEditAccount} 178 />
243 />
244 )}
245 </div> 179 </div>
246 </div> 180 </div>
247 {user.isPremium && user.isSubscriptionOwner && isUsingFranzServer && ( 181 {user.isSubscriptionOwner && isUsingFranzServer && (
248 <div className="account"> 182 <div className="account">
249 <div className="account__box"> 183 <div className="account__box">
250 <H2>{intl.formatMessage(messages.yourLicense)}</H2> 184 <H2>{intl.formatMessage(messages.yourLicense)}</H2>
251 <p> 185 <p>
252 Franz 186 Franz
253 {' '}
254 {isPremiumOverrideUser ? 'Premium' : planName}
255 {user.team.isTrial && (
256 <>
257 {' – '}
258 {intl.formatMessage(messages.trial)}
259 </>
260 )}
261 </p> 187 </p>
262 {user.team.isTrial && (
263 <>
264 <br />
265 <p>
266 {intl.formatMessage(messages.trialEndsIn, {
267 duration: moment
268 .duration(
269 moment().diff(user.team.trialEnd),
270 )
271 .humanize(),
272 })}
273 </p>
274 <p>
275 {intl.formatMessage(
276 messages.trialUpdateBillingInformation,
277 {
278 license: planName,
279 },
280 )}
281 </p>
282 </>
283 )}
284 {!isProUser && (
285 <div className="manage-user-links">
286 <Button
287 label={intl.formatMessage(
288 messages.upgradeAccountToPro,
289 )}
290 className="franz-form__button--primary"
291 onClick={upgradeToPro}
292 />
293 </div>
294 )}
295 <div className="manage-user-links"> 188 <div className="manage-user-links">
296 <Button 189 <Button
297 label={intl.formatMessage( 190 label={intl.formatMessage(
298 messages.manageSubscriptionButtonLabel,
299 )}
300 className="franz-form__button--inverted"
301 onClick={openBilling}
302 />
303 <Button
304 label={intl.formatMessage(
305 messages.invoicesButton, 191 messages.invoicesButton,
306 )} 192 )}
307 className="franz-form__button--inverted" 193 className="franz-form__button--inverted"
@@ -311,15 +197,6 @@ class AccountDashboard extends Component {
311 </div> 197 </div>
312 </div> 198 </div>
313 )} 199 )}
314 {!user.isPremium && (
315 <div className="account franz-form">
316 <div className="account__box">
317 <SubscriptionForm
318 onCloseWindow={onCloseSubscriptionWindow}
319 />
320 </div>
321 </div>
322 )}
323 </> 200 </>
324 )} 201 )}
325 202
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index cebab2f12..02cae6b69 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import 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';
6import { RouterStore } from 'mobx-react-router'; 5import { RouterStore } from 'mobx-react-router';
7 6
8import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config'; 7import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config';
@@ -11,7 +10,6 @@ import { workspaceStore } from '../../../features/workspaces';
11import UIStore from '../../../stores/UIStore'; 10import UIStore from '../../../stores/UIStore';
12import SettingsStore from '../../../stores/SettingsStore'; 11import SettingsStore from '../../../stores/SettingsStore';
13import UserStore from '../../../stores/UserStore'; 12import UserStore from '../../../stores/UserStore';
14import { serviceLimitStore } from '../../../features/serviceLimit';
15 13
16const messages = defineMessages({ 14const messages = defineMessages({
17 availableServices: { 15 availableServices: {
@@ -98,8 +96,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
98 96
99 render() { 97 render() {
100 const { serviceCount, workspaceCount, stores } = this.props; 98 const { serviceCount, workspaceCount, stores } = this.props;
101 const { isDarkThemeActive } = stores.ui;
102 const { router, user } = stores;
103 const { intl } = this.context; 99 const { intl } = this.context;
104 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 100 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
105 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER; 101 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER;
@@ -124,9 +120,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
124 {' '} 120 {' '}
125 <span className="badge"> 121 <span className="badge">
126 {serviceCount} 122 {serviceCount}
127 {serviceLimitStore.serviceLimit !== 0 && (
128 `/${serviceLimitStore.serviceLimit}`
129 )}
130 </span> 123 </span>
131 </Link> 124 </Link>
132 {workspaceStore.isFeatureEnabled ? ( 125 {workspaceStore.isFeatureEnabled ? (
@@ -138,11 +131,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
138 > 131 >
139 {intl.formatMessage(messages.yourWorkspaces)} 132 {intl.formatMessage(messages.yourWorkspaces)}
140 {' '} 133 {' '}
141 {workspaceStore.isPremiumUpgradeRequired ? ( 134 <span className="badge">{workspaceCount}</span>
142 <ProBadge inverted={!isDarkThemeActive && workspaceStore.isSettingsRouteActive} />
143 ) : (
144 <span className="badge">{workspaceCount}</span>
145 )}
146 </Link> 135 </Link>
147 ) : null} 136 ) : null}
148 {!isUsingWithoutAccount && ( 137 {!isUsingWithoutAccount && (
@@ -163,9 +152,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
163 disabled={!isLoggedIn} 152 disabled={!isLoggedIn}
164 > 153 >
165 {intl.formatMessage(messages.team)} 154 {intl.formatMessage(messages.team)}
166 {!user.data.isPremium && (
167 <ProBadge inverted={!isDarkThemeActive && router.location.pathname === '/settings/team'} />
168 )}
169 </Link> 155 </Link>
170 )} 156 )}
171 <Link 157 <Link
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js
index 12e3775f6..55f415bd5 100644
--- a/src/components/settings/recipes/RecipeItem.js
+++ b/src/components/settings/recipes/RecipeItem.js
@@ -28,6 +28,11 @@ export default @observer class RecipeItem extends Component {
28 alt="" 28 alt=""
29 /> 29 />
30 <span className="recipe-teaser__label">{recipe.name}</span> 30 <span className="recipe-teaser__label">{recipe.name}</span>
31 {recipe.aliases && recipe.aliases.length > 0 && (
32 <span className="recipe-teaser__alias_label">
33 {`Aliases: ${recipe.aliases.join(', ')}`}
34 </span>
35 )}
31 </button> 36 </button>
32 ); 37 );
33 } 38 }
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index b4e2fc05c..44ff2d0d7 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -6,15 +6,13 @@ import { Link } from 'react-router';
6 6
7import { Button, Input } from '@meetfranz/forms'; 7import { Button, Input } from '@meetfranz/forms';
8import injectSheet from 'react-jss'; 8import injectSheet from 'react-jss';
9import { H3, H2, ProBadge } from '@meetfranz/ui'; 9import { H3, H2 } from '@meetfranz/ui';
10import SearchInput from '../../ui/SearchInput'; 10import SearchInput from '../../ui/SearchInput';
11import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
12import RecipeItem from './RecipeItem'; 12import RecipeItem from './RecipeItem';
13import Loader from '../../ui/Loader'; 13import Loader from '../../ui/Loader';
14import Appear from '../../ui/effects/Appear'; 14import Appear from '../../ui/effects/Appear';
15import { FRANZ_SERVICE_REQUEST } from '../../../config'; 15import { FRANZ_SERVICE_REQUEST } from '../../../config';
16import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
17import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
18import RecipePreview from '../../../models/RecipePreview'; 16import RecipePreview from '../../../models/RecipePreview';
19 17
20const messages = defineMessages({ 18const messages = defineMessages({
@@ -26,10 +24,6 @@ const messages = defineMessages({
26 id: 'settings.searchService', 24 id: 'settings.searchService',
27 defaultMessage: '!!!Search service', 25 defaultMessage: '!!!Search service',
28 }, 26 },
29 mostPopularRecipes: {
30 id: 'settings.recipes.mostPopular',
31 defaultMessage: '!!!Most popular',
32 },
33 allRecipes: { 27 allRecipes: {
34 id: 'settings.recipes.all', 28 id: 'settings.recipes.all',
35 defaultMessage: '!!!All services', 29 defaultMessage: '!!!All services',
@@ -109,7 +103,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
109 recipes: MobxPropTypes.arrayOrObservableArray.isRequired, 103 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
110 customWebsiteRecipe: PropTypes.instanceOf(RecipePreview).isRequired, 104 customWebsiteRecipe: PropTypes.instanceOf(RecipePreview).isRequired,
111 isLoading: PropTypes.bool.isRequired, 105 isLoading: PropTypes.bool.isRequired,
112 hasLoadedRecipes: PropTypes.bool.isRequired,
113 showAddServiceInterface: PropTypes.func.isRequired, 106 showAddServiceInterface: PropTypes.func.isRequired,
114 searchRecipes: PropTypes.func.isRequired, 107 searchRecipes: PropTypes.func.isRequired,
115 resetSearch: PropTypes.func.isRequired, 108 resetSearch: PropTypes.func.isRequired,
@@ -120,7 +113,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
120 openRecipeDirectory: PropTypes.func.isRequired, 113 openRecipeDirectory: PropTypes.func.isRequired,
121 openDevDocs: PropTypes.func.isRequired, 114 openDevDocs: PropTypes.func.isRequired,
122 classes: PropTypes.object.isRequired, 115 classes: PropTypes.object.isRequired,
123 isCommunityRecipesIncludedInCurrentPlan: PropTypes.bool.isRequired,
124 }; 116 };
125 117
126 static defaultProps = { 118 static defaultProps = {
@@ -137,7 +129,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
137 recipes, 129 recipes,
138 customWebsiteRecipe, 130 customWebsiteRecipe,
139 isLoading, 131 isLoading,
140 hasLoadedRecipes,
141 showAddServiceInterface, 132 showAddServiceInterface,
142 searchRecipes, 133 searchRecipes,
143 resetSearch, 134 resetSearch,
@@ -148,11 +139,9 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
148 openRecipeDirectory, 139 openRecipeDirectory,
149 openDevDocs, 140 openDevDocs,
150 classes, 141 classes,
151 isCommunityRecipesIncludedInCurrentPlan,
152 } = this.props; 142 } = this.props;
153 const { intl } = this.context; 143 const { intl } = this.context;
154 144
155
156 const communityRecipes = recipes.filter(r => !r.isDevRecipe); 145 const communityRecipes = recipes.filter(r => !r.isDevRecipe);
157 const devRecipes = recipes.filter(r => r.isDevRecipe); 146 const devRecipes = recipes.filter(r => r.isDevRecipe);
158 147
@@ -163,7 +152,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
163 <div className="settings__header"> 152 <div className="settings__header">
164 <h1>{intl.formatMessage(messages.headline)}</h1> 153 <h1>{intl.formatMessage(messages.headline)}</h1>
165 </div> 154 </div>
166 <LimitReachedInfobox />
167 <div className="settings__body recipes"> 155 <div className="settings__body recipes">
168 {serviceStatus.length > 0 && serviceStatus.includes('created') && ( 156 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
169 <Appear> 157 <Appear>
@@ -190,14 +178,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
190 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} 178 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
191 onClick={() => resetSearch()} 179 onClick={() => resetSearch()}
192 > 180 >
193 {intl.formatMessage(messages.mostPopularRecipes)}
194 </Link>
195 <Link
196 to="/settings/recipes/all"
197 className="badge"
198 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
199 onClick={() => resetSearch()}
200 >
201 {intl.formatMessage(messages.allRecipes)} 181 {intl.formatMessage(messages.allRecipes)}
202 </Link> 182 </Link>
203 <Link 183 <Link
@@ -208,7 +188,7 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
208 > 188 >
209 {intl.formatMessage(messages.customRecipes)} 189 {intl.formatMessage(messages.customRecipes)}
210 </Link> 190 </Link>
211 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> 191 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request" rel="noreferrer">
212 {intl.formatMessage(messages.missingService)} 192 {intl.formatMessage(messages.missingService)}
213 {' '} 193 {' '}
214 <i className="mdi mdi-open-in-new" /> 194 <i className="mdi mdi-open-in-new" />
@@ -223,9 +203,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
223 <> 203 <>
224 <H2> 204 <H2>
225 {intl.formatMessage(messages.headlineCustomRecipes)} 205 {intl.formatMessage(messages.headlineCustomRecipes)}
226 {!isCommunityRecipesIncludedInCurrentPlan && (
227 <ProBadge className={classes.proBadge} />
228 )}
229 </H2> 206 </H2>
230 <div className={classes.devRecipeIntroContainer}> 207 <div className={classes.devRecipeIntroContainer}>
231 <p> 208 <p>
@@ -251,37 +228,33 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
251 </div> 228 </div>
252 </> 229 </>
253 )} 230 )}
254 <PremiumFeatureContainer 231 {recipeFilter === 'dev' && communityRecipes.length > 0 && (
255 condition={(recipeFilter === 'dev' && communityRecipes.length > 0) && !isCommunityRecipesIncludedInCurrentPlan} 232 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3>
256 > 233 )}
257 {recipeFilter === 'dev' && communityRecipes.length > 0 && ( 234 <div className="recipes__list">
258 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3> 235 {recipes.length === 0 && recipeFilter !== 'dev' && (
259 )} 236 <div className="align-middle settings__empty-state">
260 <div className="recipes__list"> 237 <span className="emoji">
261 {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && ( 238 <img src="./assets/images/emoji/dontknow.png" alt="" />
262 <div className="align-middle settings__empty-state"> 239 </span>
263 <span className="emoji">
264 <img src="./assets/images/emoji/dontknow.png" alt="" />
265 </span>
266 240
267 <p className="settings__empty-state-text">{intl.formatMessage(messages.nothingFound)}</p> 241 <p className="settings__empty-state-text">{intl.formatMessage(messages.nothingFound)}</p>
268 242
269 <RecipeItem
270 key={customWebsiteRecipe.id}
271 recipe={customWebsiteRecipe}
272 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: customWebsiteRecipe.id })}
273 />
274 </div>
275 )}
276 {communityRecipes.map(recipe => (
277 <RecipeItem 243 <RecipeItem
278 key={recipe.id} 244 key={customWebsiteRecipe.id}
279 recipe={recipe} 245 recipe={customWebsiteRecipe}
280 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })} 246 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: customWebsiteRecipe.id })}
281 /> 247 />
282 ))} 248 </div>
283 </div> 249 )}
284 </PremiumFeatureContainer> 250 {communityRecipes.map(recipe => (
251 <RecipeItem
252 key={recipe.id}
253 recipe={recipe}
254 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })}
255 />
256 ))}
257 </div>
285 {recipeFilter === 'dev' && devRecipes.length > 0 && ( 258 {recipeFilter === 'dev' && devRecipes.length > 0 && (
286 <div className={classes.devRecipeList}> 259 <div className={classes.devRecipeList}>
287 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3> 260 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3>
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 513c75eed..c41cdd56a 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -1,4 +1,4 @@
1import React, { Component, Fragment } 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 { Link } from 'react-router'; 4import { Link } from 'react-router';
@@ -6,7 +6,6 @@ import { defineMessages, intlShape } from 'react-intl';
6import normalizeUrl from 'normalize-url'; 6import normalizeUrl from 'normalize-url';
7 7
8import Form from '../../../lib/Form'; 8import Form from '../../../lib/Form';
9import User from '../../../models/User';
10import Recipe from '../../../models/Recipe'; 9import Recipe from '../../../models/Recipe';
11import Service from '../../../models/Service'; 10import Service from '../../../models/Service';
12import Tabs, { TabItem } from '../../ui/Tabs'; 11import Tabs, { TabItem } from '../../ui/Tabs';
@@ -17,9 +16,6 @@ import Button from '../../ui/Button';
17import ImageUpload from '../../ui/ImageUpload'; 16import ImageUpload from '../../ui/ImageUpload';
18import Select from '../../ui/Select'; 17import Select from '../../ui/Select';
19 18
20import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
21import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
22import { serviceLimitStore } from '../../../features/serviceLimit';
23import { isMac } from '../../../environment'; 19import { isMac } from '../../../environment';
24import globalMessages from '../../../i18n/globalMessages'; 20import globalMessages from '../../../i18n/globalMessages';
25 21
@@ -80,14 +76,6 @@ const messages = defineMessages({
80 id: 'settings.service.form.customUrlValidationError', 76 id: 'settings.service.form.customUrlValidationError',
81 defaultMessage: '!!!Could not validate custom {name} server.', 77 defaultMessage: '!!!Could not validate custom {name} server.',
82 }, 78 },
83 customUrlPremiumInfo: {
84 id: 'settings.service.form.customUrlPremiumInfo',
85 defaultMessage: '!!!To add self hosted services, you need a Ferdi Premium Supporter Account.',
86 },
87 customUrlUpgradeAccount: {
88 id: 'settings.service.form.customUrlUpgradeAccount',
89 defaultMessage: '!!!Upgrade your account',
90 },
91 indirectMessageInfo: { 79 indirectMessageInfo: {
92 id: 'settings.service.form.indirectMessageInfo', 80 id: 'settings.service.form.indirectMessageInfo',
93 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', 81 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
@@ -149,7 +137,6 @@ export default @observer class EditServiceForm extends Component {
149 137
150 return null; 138 return null;
151 }, 139 },
152 user: PropTypes.instanceOf(User).isRequired,
153 action: PropTypes.string.isRequired, 140 action: PropTypes.string.isRequired,
154 form: PropTypes.instanceOf(Form).isRequired, 141 form: PropTypes.instanceOf(Form).isRequired,
155 onSubmit: PropTypes.func.isRequired, 142 onSubmit: PropTypes.func.isRequired,
@@ -158,9 +145,6 @@ export default @observer class EditServiceForm extends Component {
158 isSaving: PropTypes.bool.isRequired, 145 isSaving: PropTypes.bool.isRequired,
159 isDeleting: PropTypes.bool.isRequired, 146 isDeleting: PropTypes.bool.isRequired,
160 isProxyFeatureEnabled: PropTypes.bool.isRequired, 147 isProxyFeatureEnabled: PropTypes.bool.isRequired,
161 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired,
162 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
163 isHibernationFeatureActive: PropTypes.bool.isRequired,
164 }; 148 };
165 149
166 static defaultProps = { 150 static defaultProps = {
@@ -217,16 +201,12 @@ export default @observer class EditServiceForm extends Component {
217 recipe, 201 recipe,
218 service, 202 service,
219 action, 203 action,
220 user,
221 form, 204 form,
222 isSaving, 205 isSaving,
223 isDeleting, 206 isDeleting,
224 onDelete, 207 onDelete,
225 openRecipeFile, 208 openRecipeFile,
226 isProxyFeatureEnabled, 209 isProxyFeatureEnabled,
227 isServiceProxyIncludedInCurrentPlan,
228 isSpellcheckerIncludedInCurrentPlan,
229 isHibernationFeatureActive,
230 } = this.props; 210 } = this.props;
231 const { intl } = this.context; 211 const { intl } = this.context;
232 212
@@ -285,9 +265,8 @@ export default @observer class EditServiceForm extends Component {
285 )} 265 )}
286 </span> 266 </span>
287 </div> 267 </div>
288 <LimitReachedInfobox />
289 <div className="settings__body"> 268 <div className="settings__body">
290 <form onSubmit={e => this.submit(e)} id="form"> 269 <form onSubmit={(e) => this.submit(e)} id="form">
291 <div className="service-name"> 270 <div className="service-name">
292 <Input field={form.$('name')} focus /> 271 <Input field={form.$('name')} focus />
293 </div> 272 </div>
@@ -311,24 +290,11 @@ export default @observer class EditServiceForm extends Component {
311 )} 290 )}
312 {recipe.hasCustomUrl && ( 291 {recipe.hasCustomUrl && (
313 <TabItem title={intl.formatMessage(messages.tabOnPremise)}> 292 <TabItem title={intl.formatMessage(messages.tabOnPremise)}>
314 {user.isPremium || recipe.author.find(a => a.email === user.email) ? ( 293 <Input field={form.$('customUrl')} />
315 <> 294 {form.error === 'url-validation-error' && (
316 <Input field={form.$('customUrl')} /> 295 <p className="franz-form__error">
317 {form.error === 'url-validation-error' && ( 296 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
318 <p className="franz-form__error"> 297 </p>
319 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
320 </p>
321 )}
322 </>
323 ) : (
324 <div className="center premium-info">
325 <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p>
326 <p>
327 <Link to="/settings/user" className="button">
328 {intl.formatMessage(messages.customUrlUpgradeAccount)}
329 </Link>
330 </p>
331 </div>
332 )} 298 )}
333 </TabItem> 299 </TabItem>
334 )} 300 )}
@@ -373,14 +339,10 @@ export default @observer class EditServiceForm extends Component {
373 <div className="settings__settings-group"> 339 <div className="settings__settings-group">
374 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> 340 <h3>{intl.formatMessage(messages.headlineGeneral)}</h3>
375 <Toggle field={form.$('isEnabled')} /> 341 <Toggle field={form.$('isEnabled')} />
376 {isHibernationFeatureActive && ( 342 <Toggle field={form.$('isHibernationEnabled')} />
377 <> 343 <p className="settings__help indented__help">
378 <Toggle field={form.$('isHibernationEnabled')} /> 344 {intl.formatMessage(messages.isHibernationEnabledInfo)}
379 <p className="settings__help indented__help"> 345 </p>
380 {intl.formatMessage(messages.isHibernationEnabledInfo)}
381 </p>
382 </>
383 )}
384 <Toggle field={form.$('isDarkModeEnabled')} /> 346 <Toggle field={form.$('isDarkModeEnabled')} />
385 {form.$('isDarkModeEnabled').value 347 {form.$('isDarkModeEnabled').value
386 && ( 348 && (
@@ -403,56 +365,46 @@ export default @observer class EditServiceForm extends Component {
403 </div> 365 </div>
404 366
405 {!isMac && ( 367 {!isMac && (
406 <PremiumFeatureContainer 368 <div className="settings__settings-group">
407 condition={!isSpellcheckerIncludedInCurrentPlan} 369 <Select field={form.$('spellcheckerLanguage')} />
408 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 370 </div>
409 >
410 <div className="settings__settings-group">
411 <Select field={form.$('spellcheckerLanguage')} />
412 </div>
413 </PremiumFeatureContainer>
414 )} 371 )}
415 372
416 {isProxyFeatureEnabled && ( 373 {isProxyFeatureEnabled && (
417 <PremiumFeatureContainer 374 <div className="settings__settings-group">
418 condition={!isServiceProxyIncludedInCurrentPlan} 375 <h3>
419 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }} 376 {intl.formatMessage(messages.headlineProxy)}
420 > 377 <span className="badge badge--success">beta</span>
421 <div className="settings__settings-group"> 378 </h3>
422 <h3> 379 <Toggle field={form.$('proxy.isEnabled')} />
423 {intl.formatMessage(messages.headlineProxy)} 380 {form.$('proxy.isEnabled').value && (
424 <span className="badge badge--success">beta</span> 381 <>
425 </h3> 382 <div className="grid">
426 <Toggle field={form.$('proxy.isEnabled')} /> 383 <div className="grid__row">
427 {form.$('proxy.isEnabled').value && ( 384 <Input field={form.$('proxy.host')} className="proxyHost" />
428 <> 385 <Input field={form.$('proxy.port')} />
429 <div className="grid">
430 <div className="grid__row">
431 <Input field={form.$('proxy.host')} className="proxyHost" />
432 <Input field={form.$('proxy.port')} />
433 </div>
434 </div> 386 </div>
435 <div className="grid"> 387 </div>
436 <div className="grid__row"> 388 <div className="grid">
437 <Input field={form.$('proxy.user')} /> 389 <div className="grid__row">
438 <Input 390 <Input field={form.$('proxy.user')} />
439 field={form.$('proxy.password')} 391 <Input
440 showPasswordToggle 392 field={form.$('proxy.password')}
441 /> 393 showPasswordToggle
442 </div> 394 />
443 </div> 395 </div>
444 <p> 396 </div>
445 <span className="mdi mdi-information" /> 397 <p>
446 {intl.formatMessage(messages.proxyRestartInfo)} 398 <span className="mdi mdi-information" />
447 </p> 399 {intl.formatMessage(messages.proxyRestartInfo)}
448 <p> 400 </p>
449 <span className="mdi mdi-information" /> 401 <p>
450 {intl.formatMessage(messages.proxyInfo)} 402 <span className="mdi mdi-information" />
451 </p> 403 {intl.formatMessage(messages.proxyInfo)}
452 </> 404 </p>
453 )} 405 </>
454 </div> 406 )}
455 </PremiumFeatureContainer> 407 </div>
456 )} 408 )}
457 409
458 <div className="user-agent"> 410 <div className="user-agent">
@@ -512,7 +464,7 @@ export default @observer class EditServiceForm extends Component {
512 type="submit" 464 type="submit"
513 label={intl.formatMessage(messages.saveService)} 465 label={intl.formatMessage(messages.saveService)}
514 htmlForm="form" 466 htmlForm="form"
515 disabled={action !== 'edit' && ((form.isPristine && requiresUserInput) || serviceLimitStore.userHasReachedServiceLimit)} 467 disabled={action !== 'edit' && (form.isPristine && requiresUserInput)}
516 /> 468 />
517 )} 469 )}
518 </div> 470 </div>
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index a0f05fd20..11d3eaa79 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -10,7 +10,6 @@ import Loader from '../../ui/Loader';
10import FAB from '../../ui/FAB'; 10import FAB from '../../ui/FAB';
11import ServiceItem from './ServiceItem'; 11import ServiceItem from './ServiceItem';
12import Appear from '../../ui/effects/Appear'; 12import Appear from '../../ui/effects/Appear';
13import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
14 13
15const messages = defineMessages({ 14const messages = defineMessages({
16 headline: { 15 headline: {
@@ -93,12 +92,11 @@ export default @observer class ServicesDashboard extends Component {
93 <div className="settings__header"> 92 <div className="settings__header">
94 <h1>{intl.formatMessage(messages.headline)}</h1> 93 <h1>{intl.formatMessage(messages.headline)}</h1>
95 </div> 94 </div>
96 <LimitReachedInfobox />
97 <div className="settings__body"> 95 <div className="settings__body">
98 {(services.length !== 0 || searchNeedle) && !isLoading && ( 96 {(services.length !== 0 || searchNeedle) && !isLoading && (
99 <SearchInput 97 <SearchInput
100 placeholder={intl.formatMessage(messages.searchService)} 98 placeholder={intl.formatMessage(messages.searchService)}
101 onChange={needle => filterServices({ needle })} 99 onChange={(needle) => filterServices({ needle })}
102 onReset={() => resetFilter()} 100 onReset={() => resetFilter()}
103 autoFocus 101 autoFocus
104 /> 102 />
@@ -165,7 +163,7 @@ export default @observer class ServicesDashboard extends Component {
165 ) : ( 163 ) : (
166 <table className="service-table"> 164 <table className="service-table">
167 <tbody> 165 <tbody>
168 {services.map(service => ( 166 {services.map((service) => (
169 <ServiceItem 167 <ServiceItem
170 key={service.id} 168 key={service.id}
171 service={service} 169 service={service}
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 52b26d65b..87a4ada27 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -1,5 +1,5 @@
1import { app, systemPreferences } from '@electron/remote'; 1import { app, systemPreferences } from '@electron/remote';
2import React, { Component, Fragment } from 'react'; 2import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
5import prettyBytes from 'pretty-bytes'; 5import prettyBytes from 'pretty-bytes';
@@ -10,14 +10,13 @@ import Button from '../../ui/Button';
10import Toggle from '../../ui/Toggle'; 10import Toggle from '../../ui/Toggle';
11import ToggleRaw from '../../ui/ToggleRaw'; 11import ToggleRaw from '../../ui/ToggleRaw';
12import Select from '../../ui/Select'; 12import Select from '../../ui/Select';
13import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
14import Input from '../../ui/Input'; 13import Input from '../../ui/Input';
15 14
16import { 15import {
17 FRANZ_TRANSLATION, 16 FRANZ_TRANSLATION,
18 GITHUB_FRANZ_URL, 17 GITHUB_FRANZ_URL,
19} from '../../../config'; 18} from '../../../config';
20import { DEFAULT_APP_SETTINGS, isMac, isWindows } from '../../../environment'; 19import { DEFAULT_APP_SETTINGS, isMac, isWindows, lockFerdiShortcutKey } from '../../../environment';
21import globalMessages from '../../../i18n/globalMessages'; 20import globalMessages from '../../../i18n/globalMessages';
22 21
23const messages = defineMessages({ 22const messages = defineMessages({
@@ -31,7 +30,7 @@ const messages = defineMessages({
31 }, 30 },
32 sentryInfo: { 31 sentryInfo: {
33 id: 'settings.app.sentryInfo', 32 id: 'settings.app.sentryInfo',
34 defaultMessage: '!!!Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data! Changing this option requires you to restart Ferdi.', 33 defaultMessage: '!!!Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data!',
35 }, 34 },
36 hibernateInfo: { 35 hibernateInfo: {
37 id: 'settings.app.hibernateInfo', 36 id: 'settings.app.hibernateInfo',
@@ -55,7 +54,7 @@ const messages = defineMessages({
55 }, 54 },
56 lockInfo: { 55 lockInfo: {
57 id: 'settings.app.lockInfo', 56 id: 'settings.app.lockInfo',
58 defaultMessage: '!!!Password Lock allows you to keep your messages protected.\nUsing 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.', 57 defaultMessage: '!!!Password Lock allows you to keep your messages protected.\nUsing 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 {lockShortcut}.',
59 }, 58 },
60 scheduledDNDTimeInfo: { 59 scheduledDNDTimeInfo: {
61 id: 'settings.app.scheduledDNDTimeInfo', 60 id: 'settings.app.scheduledDNDTimeInfo',
@@ -168,12 +167,10 @@ export default @observer class EditSettingsForm extends Component {
168 isClearingAllCache: PropTypes.bool.isRequired, 167 isClearingAllCache: PropTypes.bool.isRequired,
169 onClearAllCache: PropTypes.func.isRequired, 168 onClearAllCache: PropTypes.func.isRequired,
170 getCacheSize: PropTypes.func.isRequired, 169 getCacheSize: PropTypes.func.isRequired,
171 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
172 isTodosEnabled: PropTypes.bool.isRequired, 170 isTodosEnabled: PropTypes.bool.isRequired,
173 isTodosActivated: PropTypes.bool.isRequired, 171 isTodosActivated: PropTypes.bool.isRequired,
174 isWorkspaceEnabled: PropTypes.bool.isRequired, 172 isWorkspaceEnabled: PropTypes.bool.isRequired,
175 automaticUpdates: PropTypes.bool.isRequired, 173 automaticUpdates: PropTypes.bool.isRequired,
176 hibernationEnabled: PropTypes.bool.isRequired,
177 isDarkmodeEnabled: PropTypes.bool.isRequired, 174 isDarkmodeEnabled: PropTypes.bool.isRequired,
178 isAdaptableDarkModeEnabled: PropTypes.bool.isRequired, 175 isAdaptableDarkModeEnabled: PropTypes.bool.isRequired,
179 isNightlyEnabled: PropTypes.bool.isRequired, 176 isNightlyEnabled: PropTypes.bool.isRequired,
@@ -224,11 +221,9 @@ export default @observer class EditSettingsForm extends Component {
224 isClearingAllCache, 221 isClearingAllCache,
225 onClearAllCache, 222 onClearAllCache,
226 getCacheSize, 223 getCacheSize,
227 isSpellcheckerIncludedInCurrentPlan,
228 isTodosEnabled, 224 isTodosEnabled,
229 isWorkspaceEnabled, 225 isWorkspaceEnabled,
230 automaticUpdates, 226 automaticUpdates,
231 hibernationEnabled,
232 isDarkmodeEnabled, 227 isDarkmodeEnabled,
233 isTodosActivated, 228 isTodosActivated,
234 isNightlyEnabled, 229 isNightlyEnabled,
@@ -271,8 +266,8 @@ export default @observer class EditSettingsForm extends Component {
271 </div> 266 </div>
272 <div className="settings__body"> 267 <div className="settings__body">
273 <form 268 <form
274 onSubmit={e => this.submit(e)} 269 onSubmit={(e) => this.submit(e)}
275 onChange={e => this.submit(e)} 270 onChange={(e) => this.submit(e)}
276 id="form" 271 id="form"
277 > 272 >
278 {/* Titles */} 273 {/* Titles */}
@@ -339,13 +334,8 @@ export default @observer class EditSettingsForm extends Component {
339 334
340 <Hr /> 335 <Hr />
341 336
342 <Toggle field={form.$('hibernate')} /> 337 <Select field={form.$('hibernationStrategy')} />
343 {hibernationEnabled && ( 338 <Toggle field={form.$('hibernateOnStartup')} />
344 <>
345 <Select field={form.$('hibernationStrategy')} />
346 <Toggle field={form.$('hibernateOnStartup')} />
347 </>
348 )}
349 <p 339 <p
350 className="settings__message" 340 className="settings__message"
351 style={{ 341 style={{
@@ -357,14 +347,17 @@ export default @observer class EditSettingsForm extends Component {
357 </span> 347 </span>
358 </p> 348 </p>
359 349
350 <Select field={form.$('wakeUpStrategy')} />
351
360 <Hr /> 352 <Hr />
361 353
362 {isWorkspaceEnabled && ( 354 {isWorkspaceEnabled && (
363 <Toggle field={form.$('keepAllWorkspacesLoaded')} /> 355 <>
356 <Toggle field={form.$('keepAllWorkspacesLoaded')} />
357 <Hr />
358 </>
364 )} 359 )}
365 360
366 <Hr />
367
368 {isTodosEnabled && !hasAddedTodosAsService && ( 361 {isTodosEnabled && !hasAddedTodosAsService && (
369 <> 362 <>
370 <Toggle field={form.$('enableTodos')} /> 363 <Toggle field={form.$('enableTodos')} />
@@ -375,7 +368,7 @@ export default @observer class EditSettingsForm extends Component {
375 <div> 368 <div>
376 <Input 369 <Input
377 placeholder="Todo Server" 370 placeholder="Todo Server"
378 onChange={e => this.submit(e)} 371 onChange={(e) => this.submit(e)}
379 field={form.$('customTodoServer')} 372 field={form.$('customTodoServer')}
380 /> 373 />
381 <p 374 <p
@@ -409,7 +402,7 @@ export default @observer class EditSettingsForm extends Component {
409 > 402 >
410 <Input 403 <Input
411 placeholder="17:00" 404 placeholder="17:00"
412 onChange={e => this.submit(e)} 405 onChange={(e) => this.submit(e)}
413 field={form.$('scheduledDNDStart')} 406 field={form.$('scheduledDNDStart')}
414 type="time" 407 type="time"
415 /> 408 />
@@ -421,7 +414,7 @@ export default @observer class EditSettingsForm extends Component {
421 > 414 >
422 <Input 415 <Input
423 placeholder="09:00" 416 placeholder="09:00"
424 onChange={e => this.submit(e)} 417 onChange={(e) => this.submit(e)}
425 field={form.$('scheduledDNDEnd')} 418 field={form.$('scheduledDNDEnd')}
426 type="time" 419 type="time"
427 /> 420 />
@@ -488,7 +481,7 @@ export default @observer class EditSettingsForm extends Component {
488 481
489 <Input 482 <Input
490 placeholder="Accent Color" 483 placeholder="Accent Color"
491 onChange={e => this.submit(e)} 484 onChange={(e) => this.submit(e)}
492 field={form.$('accentColor')} 485 field={form.$('accentColor')}
493 /> 486 />
494 <p> 487 <p>
@@ -509,8 +502,12 @@ export default @observer class EditSettingsForm extends Component {
509 <Hr /> 502 <Hr />
510 503
511 <Select field={form.$('searchEngine')} /> 504 <Select field={form.$('searchEngine')} />
505
506 <Hr />
507
512 <Toggle field={form.$('sentry')} /> 508 <Toggle field={form.$('sentry')} />
513 <p>{intl.formatMessage(messages.sentryInfo)}</p> 509 <p className="settings__help">{intl.formatMessage(messages.sentryInfo)}</p>
510 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p>
514 511
515 <Hr /> 512 <Hr />
516 513
@@ -523,7 +520,7 @@ export default @observer class EditSettingsForm extends Component {
523 520
524 <Input 521 <Input
525 placeholder={intl.formatMessage(messages.lockedPassword)} 522 placeholder={intl.formatMessage(messages.lockedPassword)}
526 onChange={e => this.submit(e)} 523 onChange={(e) => this.submit(e)}
527 field={form.$('lockedPassword')} 524 field={form.$('lockedPassword')}
528 type="password" 525 type="password"
529 scorePassword 526 scorePassword
@@ -535,7 +532,7 @@ export default @observer class EditSettingsForm extends Component {
535 532
536 <Input 533 <Input
537 placeholder="Lock after inactivity" 534 placeholder="Lock after inactivity"
538 onChange={e => this.submit(e)} 535 onChange={(e) => this.submit(e)}
539 field={form.$('inactivityLock')} 536 field={form.$('inactivityLock')}
540 autoFocus 537 autoFocus
541 /> 538 />
@@ -551,7 +548,7 @@ export default @observer class EditSettingsForm extends Component {
551 }} 548 }}
552 > 549 >
553 <span> 550 <span>
554 { intl.formatMessage(messages.lockInfo) } 551 { intl.formatMessage(messages.lockInfo, { lockShortcut: `${lockFerdiShortcutKey(false)}` }) }
555 </span> 552 </span>
556 </p> 553 </p>
557 </div> 554 </div>
@@ -564,26 +561,24 @@ export default @observer class EditSettingsForm extends Component {
564 561
565 <Hr /> 562 <Hr />
566 563
567 <PremiumFeatureContainer 564 <Toggle
568 condition={!isSpellcheckerIncludedInCurrentPlan} 565 field={form.$('enableSpellchecking')}
569 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 566 />
570 > 567 {!isMac && form.$('enableSpellchecking').value && (
571 <> 568 <Select field={form.$('spellcheckerLanguage')} />
572 <Toggle 569 )}
573 field={form.$('enableSpellchecking')} 570 {isMac && form.$('enableSpellchecking').value && (
574 /> 571 <p className="settings__help">{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p>
575 {!isMac && form.$('enableSpellchecking').value && ( 572 )}
576 <Select field={form.$('spellcheckerLanguage')} /> 573 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p>
577 )} 574
578 {isMac && form.$('enableSpellchecking').value && ( 575 <Hr />
579 <p>{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p> 576
580 )}
581 </>
582 </PremiumFeatureContainer>
583 <a 577 <a
584 href={FRANZ_TRANSLATION} 578 href={FRANZ_TRANSLATION}
585 target="_blank" 579 target="_blank"
586 className="link" 580 className="link"
581 rel="noreferrer"
587 > 582 >
588 {intl.formatMessage(messages.translationHelp)} 583 {intl.formatMessage(messages.translationHelp)}
589 {' '} 584 {' '}
@@ -602,7 +597,7 @@ export default @observer class EditSettingsForm extends Component {
602 597
603 <Input 598 <Input
604 placeholder="User Agent" 599 placeholder="User Agent"
605 onChange={e => this.submit(e)} 600 onChange={(e) => this.submit(e)}
606 field={form.$('userAgentPref')} 601 field={form.$('userAgentPref')}
607 /> 602 />
608 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p> 603 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p>
@@ -688,12 +683,12 @@ export default @observer class EditSettingsForm extends Component {
688 683
689 Ferdi is based on 684 Ferdi is based on
690 {' '} 685 {' '}
691 <a href={`${GITHUB_FRANZ_URL}/franz`} target="_blank">Franz</a> 686 <a href={`${GITHUB_FRANZ_URL}/franz`} target="_blank" rel="noreferrer">Franz</a>
692 687
693 , a project published 688 , a project published
694 under the 689 under the
695 {' '} 690 {' '}
696 <a href={`${GITHUB_FRANZ_URL}/franz/blob/master/LICENSE`} target="_blank">Apache-2.0 License</a> 691 <a href={`${GITHUB_FRANZ_URL}/franz/blob/master/LICENSE`} target="_blank" rel="noreferrer">Apache-2.0 License</a>
697 </span> 692 </span>
698 <br /> 693 <br />
699 <span className="mdi mdi-information" /> 694 <span className="mdi mdi-information" />
diff --git a/src/components/settings/supportFerdi/SupportFerdiDashboard.js b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
index 36c126565..b84e06739 100644
--- a/src/components/settings/supportFerdi/SupportFerdiDashboard.js
+++ b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
@@ -94,10 +94,10 @@ class SupportFerdiDashboard extends Component {
94 <h1>{intl.formatMessage(messages.title)}</h1> 94 <h1>{intl.formatMessage(messages.title)}</h1>
95 <div> 95 <div>
96 <p className="settings__support-badges"> 96 <p className="settings__support-badges">
97 <a href="https://github.com/getferdi/ferdi" target="_blank"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/getferdi/ferdi?style=social" /></a> 97 <a href="https://github.com/getferdi/ferdi" target="_blank" rel="noreferrer"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/getferdi/ferdi?style=social" /></a>
98 <a href="https://twitter.com/getferdi/" target="_blank"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/getferdi?label=Follow&style=social" /></a> 98 <a href="https://twitter.com/getferdi/" target="_blank" rel="noreferrer"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/getferdi?label=Follow&style=social" /></a>
99 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank"><img alt="Open Collective backers" src="https://img.shields.io/opencollective/backers/getferdi?logo=open-collective" /></a> 99 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank" rel="noreferrer"><img alt="Open Collective backers" src="https://img.shields.io/opencollective/backers/getferdi?logo=open-collective" /></a>
100 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank"><img alt="Open Collective sponsors" src="https://img.shields.io/opencollective/sponsors/getferdi?logo=open-collective" /></a> 100 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank" rel="noreferrer"><img alt="Open Collective sponsors" src="https://img.shields.io/opencollective/sponsors/getferdi?logo=open-collective" /></a>
101 </p> 101 </p>
102 <FormattedHTMLMessage {...messages.aboutIntro} /> 102 <FormattedHTMLMessage {...messages.aboutIntro} />
103 <br /> 103 <br />
@@ -109,7 +109,7 @@ class SupportFerdiDashboard extends Component {
109 </p> 109 </p>
110 <p> 110 <p>
111 {intl.formatMessage(messages.textListContributors)} 111 {intl.formatMessage(messages.textListContributors)}
112 <a href="https://github.com/getferdi/ferdi#contributors-" target="_blank" className="link"> 112 <a href="https://github.com/getferdi/ferdi#contributors-" target="_blank" className="link" rel="noreferrer">
113 {' '} 113 {' '}
114 {intl.formatMessage(messages.textListContributorsHere)} 114 {intl.formatMessage(messages.textListContributorsHere)}
115 <i className="mdi mdi-open-in-new" /> 115 <i className="mdi mdi-open-in-new" />
@@ -122,7 +122,7 @@ class SupportFerdiDashboard extends Component {
122 </p> 122 </p>
123 <p> 123 <p>
124 {intl.formatMessage(messages.textSupportWelcome)} 124 {intl.formatMessage(messages.textSupportWelcome)}
125 <a href="https://help.getferdi.com/general/support" target="_blank" className="link"> 125 <a href="https://help.getferdi.com/general/support" target="_blank" className="link" rel="noreferrer">
126 {' '} 126 {' '}
127 {intl.formatMessage(messages.textSupportWelcomeHere)} 127 {intl.formatMessage(messages.textSupportWelcomeHere)}
128 <i className="mdi mdi-open-in-new" /> 128 <i className="mdi mdi-open-in-new" />
@@ -130,7 +130,7 @@ class SupportFerdiDashboard extends Component {
130 </p> 130 </p>
131 <p> 131 <p>
132 {intl.formatMessage(messages.textExpenses)} 132 {intl.formatMessage(messages.textExpenses)}
133 <a href="https://opencollective.com/getferdi#section-budget" target="_blank" className="link"> 133 <a href="https://opencollective.com/getferdi#section-budget" target="_blank" className="link" rel="noreferrer">
134 {' '} 134 {' '}
135 {intl.formatMessage(messages.textOpenCollective)} 135 {intl.formatMessage(messages.textOpenCollective)}
136 <i className="mdi mdi-open-in-new" /> 136 <i className="mdi mdi-open-in-new" />
@@ -138,14 +138,14 @@ class SupportFerdiDashboard extends Component {
138 </p> 138 </p>
139 <p> 139 <p>
140 {intl.formatMessage(messages.textDonation)} 140 {intl.formatMessage(messages.textDonation)}
141 <a href="https://opencollective.com/getferdi#section-contribute" target="_blank" className="link"> 141 <a href="https://opencollective.com/getferdi#section-contribute" target="_blank" className="link" rel="noreferrer">
142 {' '} 142 {' '}
143 {intl.formatMessage(messages.textOpenCollective)} 143 {intl.formatMessage(messages.textOpenCollective)}
144 <i className="mdi mdi-open-in-new" /> 144 <i className="mdi mdi-open-in-new" />
145 </a> 145 </a>
146 {' '} 146 {' '}
147 {intl.formatMessage(messages.textDonationAnd)} 147 {intl.formatMessage(messages.textDonationAnd)}
148 <a href="https://github.com/sponsors/getferdi" target="_blank" className="link"> 148 <a href="https://github.com/sponsors/getferdi" target="_blank" className="link" rel="noreferrer">
149 {' '} 149 {' '}
150 {intl.formatMessage(messages.textGitHubSponsors)} 150 {intl.formatMessage(messages.textGitHubSponsors)}
151 <i className="mdi mdi-open-in-new" /> 151 <i className="mdi mdi-open-in-new" />
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 602d6e490..437225058 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -6,12 +6,9 @@ import ReactTooltip from 'react-tooltip';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import classnames from 'classnames'; 7import classnames from 'classnames';
8 8
9import { Badge } from '@meetfranz/ui';
10import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
11import Button from '../../ui/Button'; 10import Button from '../../ui/Button';
12import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
13import globalMessages from '../../../i18n/globalMessages';
14import UpgradeButton from '../../ui/UpgradeButton';
15import { LIVE_FRANZ_API } from '../../../config'; 12import { LIVE_FRANZ_API } from '../../../config';
16 13
17const messages = defineMessages({ 14const messages = defineMessages({
@@ -35,10 +32,6 @@ const messages = defineMessages({
35 id: 'settings.team.manageAction', 32 id: 'settings.team.manageAction',
36 defaultMessage: '!!!Manage your Team on meetfranz.com', 33 defaultMessage: '!!!Manage your Team on meetfranz.com',
37 }, 34 },
38 upgradeButton: {
39 id: 'settings.team.upgradeAction',
40 defaultMessage: '!!!Upgrade your Account',
41 },
42 teamsUnavailable: { 35 teamsUnavailable: {
43 id: 'settings.team.teamsUnavailable', 36 id: 'settings.team.teamsUnavailable',
44 defaultMessage: '!!!Teams are unavailable', 37 defaultMessage: '!!!Teams are unavailable',
@@ -88,10 +81,6 @@ const styles = {
88 headlineWithSpacing: { 81 headlineWithSpacing: {
89 marginBottom: 'inherit', 82 marginBottom: 'inherit',
90 }, 83 },
91 proRequired: {
92 margin: [10, 0, 40],
93 height: 'auto',
94 },
95 buttonContainer: { 84 buttonContainer: {
96 display: 'flex', 85 display: 'flex',
97 height: 'auto', 86 height: 'auto',
@@ -105,7 +94,6 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
105 retryUserInfoRequest: PropTypes.func.isRequired, 94 retryUserInfoRequest: PropTypes.func.isRequired,
106 openTeamManagement: PropTypes.func.isRequired, 95 openTeamManagement: PropTypes.func.isRequired,
107 classes: PropTypes.object.isRequired, 96 classes: PropTypes.object.isRequired,
108 isProUser: PropTypes.bool.isRequired,
109 server: PropTypes.string.isRequired, 97 server: PropTypes.string.isRequired,
110 }; 98 };
111 99
@@ -119,7 +107,6 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
119 userInfoRequestFailed, 107 userInfoRequestFailed,
120 retryUserInfoRequest, 108 retryUserInfoRequest,
121 openTeamManagement, 109 openTeamManagement,
122 isProUser,
123 classes, 110 classes,
124 server, 111 server,
125 } = this.props; 112 } = this.props;
@@ -157,37 +144,25 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
157 <> 144 <>
158 <h1 className={classnames({ 145 <h1 className={classnames({
159 [classes.headline]: true, 146 [classes.headline]: true,
160 [classes.headlineWithSpacing]: isProUser, 147 [classes.headlineWithSpacing]: true,
161 })} 148 })}
162 > 149 >
163 {intl.formatMessage(messages.contentHeadline)} 150 {intl.formatMessage(messages.contentHeadline)}
164 151
165 </h1> 152 </h1>
166 {!isProUser && (
167 <Badge className={classes.proRequired}>{intl.formatMessage(globalMessages.proRequired)}</Badge>
168 )}
169 <div className={classes.container}> 153 <div className={classes.container}>
170 <div className={classes.content}> 154 <div className={classes.content}>
171 <p>{intl.formatMessage(messages.intro)}</p> 155 <p>{intl.formatMessage(messages.intro)}</p>
172 <p>{intl.formatMessage(messages.copy)}</p> 156 <p>{intl.formatMessage(messages.copy)}</p>
173 </div> 157 </div>
174 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" /> 158 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Ferdi for Teams" />
175 </div> 159 </div>
176 <div className={classes.buttonContainer}> 160 <div className={classes.buttonContainer}>
177 {!isProUser ? ( 161 <Button
178 <UpgradeButton 162 label={intl.formatMessage(messages.manageButton)}
179 className={classes.cta} 163 onClick={openTeamManagement}
180 gaEventInfo={{ category: 'Todos', event: 'upgrade' }} 164 className={classes.cta}
181 requiresPro 165 />
182 short
183 />
184 ) : (
185 <Button
186 label={intl.formatMessage(messages.manageButton)}
187 onClick={openTeamManagement}
188 className={classes.cta}
189 />
190 )}
191 </div> 166 </div>
192 </> 167 </>
193 </> 168 </>
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js
index a1a353e57..db78acb69 100644
--- a/src/components/settings/user/EditUserForm.js
+++ b/src/components/settings/user/EditUserForm.js
@@ -84,7 +84,7 @@ export default @observer class EditUserForm extends Component {
84 </span> 84 </span>
85 </div> 85 </div>
86 <div className="settings__body"> 86 <div className="settings__body">
87 <form onSubmit={e => this.submit(e)} id="form"> 87 <form onSubmit={(e) => this.submit(e)} id="form">
88 {status.length > 0 && status.includes('data-updated') && ( 88 {status.length > 0 && status.includes('data-updated') && (
89 <Infobox 89 <Infobox
90 type="success" 90 type="success"