aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorLibravatar vantezzen <hello@vantezzen.io>2020-01-31 21:03:49 +0100
committerLibravatar vantezzen <hello@vantezzen.io>2020-01-31 21:03:49 +0100
commitd54ceb639862788c053e21f217ca39ac36003db6 (patch)
tree40bfd8bb6a8a85c222f9c67d7dd862a835394e80 /src/components
parentAdd publish debug log option (diff)
parentMerge branch 'develop' of https://github.com/getferdi/ferdi into develop (diff)
downloadferdium-app-d54ceb639862788c053e21f217ca39ac36003db6.tar.gz
ferdium-app-d54ceb639862788c053e21f217ca39ac36003db6.tar.zst
ferdium-app-d54ceb639862788c053e21f217ca39ac36003db6.zip
Merge branch 'develop' into publish-debug
Diffstat (limited to 'src/components')
-rw-r--r--src/components/auth/Locked.js3
-rw-r--r--src/components/services/content/ServiceView.js1
-rw-r--r--src/components/settings/account/AccountDashboard.js316
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js4
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js6
-rw-r--r--src/components/settings/settings/EditSettingsForm.js99
-rw-r--r--src/components/settings/team/TeamDashboard.js9
-rw-r--r--src/components/ui/ImageUpload.js2
-rw-r--r--src/components/ui/Link.js6
-rw-r--r--src/components/ui/WebviewLoader/index.js4
10 files changed, 293 insertions, 157 deletions
diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.js
index 045621d0a..e7f3dc78b 100644
--- a/src/components/auth/Locked.js
+++ b/src/components/auth/Locked.js
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5 5
6import Form from '../../lib/Form'; 6import Form from '../../lib/Form';
7import { required } from '../../helpers/validation-helpers';
8import Input from '../ui/Input'; 7import Input from '../ui/Input';
9import Button from '../ui/Button'; 8import Button from '../ui/Button';
10import Infobox from '../ui/Infobox'; 9import Infobox from '../ui/Infobox';
@@ -50,7 +49,6 @@ export default @observer class Locked extends Component {
50 password: { 49 password: {
51 label: this.context.intl.formatMessage(messages.passwordLabel), 50 label: this.context.intl.formatMessage(messages.passwordLabel),
52 value: '', 51 value: '',
53 validators: [required],
54 type: 'password', 52 type: 'password',
55 }, 53 },
56 }, 54 },
@@ -89,6 +87,7 @@ export default @observer class Locked extends Component {
89 <Input 87 <Input
90 field={form.$('password')} 88 field={form.$('password')}
91 showPasswordToggle 89 showPasswordToggle
90 focus
92 /> 91 />
93 {error.code === 'invalid-credentials' && ( 92 {error.code === 'invalid-credentials' && (
94 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p> 93 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p>
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 1fff5ef7a..860863d26 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -96,6 +96,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
96 componentWillUnmount() { 96 componentWillUnmount() {
97 this.autorunDisposer(); 97 this.autorunDisposer();
98 clearTimeout(this.forceRepaintTimeout); 98 clearTimeout(this.forceRepaintTimeout);
99 clearTimeout(this.hibernationTimer);
99 } 100 }
100 101
101 updateTargetUrl = (event) => { 102 updateTargetUrl = (event) => {
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 83dc34a52..7d6bad883 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -3,9 +3,7 @@ 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 { 6import { ProBadge, H1, H2 } from '@meetfranz/ui';
7 ProBadge, H1, H2,
8} from '@meetfranz/ui';
9import moment from 'moment'; 7import moment from 'moment';
10 8
11import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
@@ -13,6 +11,7 @@ import Button from '../../ui/Button';
13import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
14import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; 12import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
15import { i18nPlanName } from '../../../helpers/plan-helpers'; 13import { i18nPlanName } from '../../../helpers/plan-helpers';
14import { LOCAL_SERVER } from '../../../config';
16 15
17const messages = defineMessages({ 16const messages = defineMessages({
18 headline: { 17 headline: {
@@ -69,11 +68,13 @@ const messages = defineMessages({
69 }, 68 },
70 deleteInfo: { 69 deleteInfo: {
71 id: 'settings.account.deleteInfo', 70 id: 'settings.account.deleteInfo',
72 defaultMessage: '!!!If you don\'t need your Ferdi account any longer, you can delete your account and all related data here.', 71 defaultMessage:
72 "!!!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',
76 defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 76 defaultMessage:
77 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
77 }, 78 },
78 trial: { 79 trial: {
79 id: 'settings.account.trial', 80 id: 'settings.account.trial',
@@ -89,7 +90,16 @@ const messages = defineMessages({
89 }, 90 },
90 trialUpdateBillingInformation: { 91 trialUpdateBillingInformation: {
91 id: 'settings.account.trialUpdateBillingInfo', 92 id: 'settings.account.trialUpdateBillingInfo',
92 defaultMessage: '!!!Please update your billing info to continue using {license} after your trial period.', 93 defaultMessage:
94 '!!!Please update your billing info to continue using {license} after your trial period.',
95 },
96 accountUnavailable: {
97 id: 'settings.account.accountUnavailable',
98 defaultMessage: 'Account is unavailable',
99 },
100 accountUnavailableInfo: {
101 id: 'settings.account.accountUnavailableInfo',
102 defaultMessage: 'You are using Ferdi without an account. If you want to use Ferdi with an account and keep your services synchronized across installations, please select a server in the Settings tab then login.',
93 }, 103 },
94}); 104});
95 105
@@ -110,6 +120,7 @@ class AccountDashboard extends Component {
110 upgradeToPro: PropTypes.func.isRequired, 120 upgradeToPro: PropTypes.func.isRequired,
111 openInvoices: PropTypes.func.isRequired, 121 openInvoices: PropTypes.func.isRequired,
112 onCloseSubscriptionWindow: PropTypes.func.isRequired, 122 onCloseSubscriptionWindow: PropTypes.func.isRequired,
123 server: PropTypes.string.isRequired,
113 }; 124 };
114 125
115 static contextTypes = { 126 static contextTypes = {
@@ -132,6 +143,7 @@ class AccountDashboard extends Component {
132 upgradeToPro, 143 upgradeToPro,
133 openInvoices, 144 openInvoices,
134 onCloseSubscriptionWindow, 145 onCloseSubscriptionWindow,
146 server,
135 } = this.props; 147 } = this.props;
136 const { intl } = this.context; 148 const { intl } = this.context;
137 149
@@ -141,6 +153,8 @@ class AccountDashboard extends Component {
141 planName = i18nPlanName(user.team.plan, intl); 153 planName = i18nPlanName(user.team.plan, intl);
142 } 154 }
143 155
156 const isUsingWithoutAccount = server === LOCAL_SERVER;
157
144 return ( 158 return (
145 <div className="settings__main"> 159 <div className="settings__main">
146 <div className="settings__header"> 160 <div className="settings__header">
@@ -149,154 +163,186 @@ class AccountDashboard extends Component {
149 </span> 163 </span>
150 </div> 164 </div>
151 <div className="settings__body"> 165 <div className="settings__body">
152 {isLoading && ( 166 {isUsingWithoutAccount && (
153 <Loader /> 167 <>
168 <h1 style={{ marginBottom: 0 }}>
169 {intl.formatMessage(messages.accountUnavailable)}
170 </h1>
171 <p
172 className="settings__message"
173 style={{
174 borderTop: 0,
175 marginTop: 0,
176 }}
177 >
178 {intl.formatMessage(messages.accountUnavailableInfo)}
179 </p>
180 </>
154 )} 181 )}
182 {!isUsingWithoutAccount && (
183 <>
184 {isLoading && <Loader />}
155 185
156 {!isLoading && userInfoRequestFailed && ( 186 {!isLoading && userInfoRequestFailed && (
157 <Infobox 187 <Infobox
158 icon="alert" 188 icon="alert"
159 type="danger" 189 type="danger"
160 ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} 190 ctaLabel={intl.formatMessage(
161 ctaLoading={isLoading} 191 messages.tryReloadUserInfoRequest,
162 ctaOnClick={retryUserInfoRequest} 192 )}
163 > 193 ctaLoading={isLoading}
164 {intl.formatMessage(messages.userInfoRequestFailed)} 194 ctaOnClick={retryUserInfoRequest}
165 </Infobox> 195 >
166 )} 196 {intl.formatMessage(messages.userInfoRequestFailed)}
197 </Infobox>
198 )}
167 199
168 {!userInfoRequestFailed && ( 200 {!userInfoRequestFailed && (
169 <>
170 {!isLoading && (
171 <> 201 <>
172 <div className="account"> 202 {!isLoading && (
173 <div className="account__box account__box--flex"> 203 <>
174 <div className="account__avatar"> 204 <div className="account">
175 <img 205 <div className="account__box account__box--flex">
176 src="./assets/images/logo.svg" 206 <div className="account__avatar">
177 alt="" 207 <img src="./assets/images/logo.svg" alt="" />
178 /> 208 </div>
179 </div> 209 <div className="account__info">
180 <div className="account__info"> 210 <H1>
181 <H1> 211 <span className="username">{`${user.firstname} ${user.lastname}`}</span>
182 <span className="username">{`${user.firstname} ${user.lastname}`}</span> 212 {user.isPremium && (
183 {user.isPremium && ( 213 <>
184 <> 214 {' '}
185 {' '} 215 <ProBadge />
186 <ProBadge /> 216 </>
187 </> 217 )}
188 )} 218 </H1>
189 </H1> 219 <p>
190 <p> 220 {user.organization && `${user.organization}, `}
191 {user.organization && `${user.organization}, `} 221 {user.email}
192 {user.email} 222 </p>
193 </p> 223 {user.isPremium && (
194 {user.isPremium && ( 224 <div className="manage-user-links">
195 <div className="manage-user-links"> 225 <Button
226 label={intl.formatMessage(
227 messages.accountEditButton,
228 )}
229 className="franz-form__button--inverted"
230 onClick={openEditAccount}
231 />
232 </div>
233 )}
234 </div>
235 {!user.isPremium && (
196 <Button 236 <Button
197 label={intl.formatMessage(messages.accountEditButton)} 237 label={intl.formatMessage(
238 messages.accountEditButton,
239 )}
198 className="franz-form__button--inverted" 240 className="franz-form__button--inverted"
199 onClick={openEditAccount} 241 onClick={openEditAccount}
200 /> 242 />
201 </div>
202 )}
203 </div>
204 {!user.isPremium && (
205 <Button
206 label={intl.formatMessage(messages.accountEditButton)}
207 className="franz-form__button--inverted"
208 onClick={openEditAccount}
209 />
210 )}
211 </div>
212 </div>
213 {user.isPremium && user.isSubscriptionOwner && (
214 <div className="account">
215 <div className="account__box">
216 <H2>
217 {intl.formatMessage(messages.yourLicense)}
218 </H2>
219 <p>
220 Franz
221 {' '}
222 {isPremiumOverrideUser ? 'Premium' : planName}
223 {user.team.isTrial && (
224 <>
225 {' – '}
226 {intl.formatMessage(messages.trial)}
227 </>
228 )} 243 )}
229 </p> 244 </div>
230 {user.team.isTrial && ( 245 </div>
231 <> 246 {user.isPremium && user.isSubscriptionOwner && (
232 <br /> 247 <div className="account">
233 <p> 248 <div className="account__box">
234 {intl.formatMessage(messages.trialEndsIn, { 249 <H2>{intl.formatMessage(messages.yourLicense)}</H2>
235 duration: moment.duration(moment().diff(user.team.trialEnd)).humanize(),
236 })}
237 </p>
238 <p> 250 <p>
239 {intl.formatMessage(messages.trialUpdateBillingInformation, { 251 Franz
240 license: planName, 252 {' '}
241 })} 253 {isPremiumOverrideUser ? 'Premium' : planName}
254 {user.team.isTrial && (
255 <>
256 {' – '}
257 {intl.formatMessage(messages.trial)}
258 </>
259 )}
242 </p> 260 </p>
243 </> 261 {user.team.isTrial && (
244 )} 262 <>
245 {!isProUser && ( 263 <br />
246 <div className="manage-user-links"> 264 <p>
247 <Button 265 {intl.formatMessage(messages.trialEndsIn, {
248 label={intl.formatMessage(messages.upgradeAccountToPro)} 266 duration: moment
249 className="franz-form__button--primary" 267 .duration(
250 onClick={upgradeToPro} 268 moment().diff(user.team.trialEnd),
269 )
270 .humanize(),
271 })}
272 </p>
273 <p>
274 {intl.formatMessage(
275 messages.trialUpdateBillingInformation,
276 {
277 license: planName,
278 },
279 )}
280 </p>
281 </>
282 )}
283 {!isProUser && (
284 <div className="manage-user-links">
285 <Button
286 label={intl.formatMessage(
287 messages.upgradeAccountToPro,
288 )}
289 className="franz-form__button--primary"
290 onClick={upgradeToPro}
291 />
292 </div>
293 )}
294 <div className="manage-user-links">
295 <Button
296 label={intl.formatMessage(
297 messages.manageSubscriptionButtonLabel,
298 )}
299 className="franz-form__button--inverted"
300 onClick={openBilling}
301 />
302 <Button
303 label={intl.formatMessage(
304 messages.invoicesButton,
305 )}
306 className="franz-form__button--inverted"
307 onClick={openInvoices}
308 />
309 </div>
310 </div>
311 </div>
312 )}
313 {!user.isPremium && (
314 <div className="account franz-form">
315 <div className="account__box">
316 <SubscriptionForm
317 onCloseWindow={onCloseSubscriptionWindow}
251 /> 318 />
252 </div> 319 </div>
253 )} 320 </div>
254 <div className="manage-user-links"> 321 )}
255 <Button 322 </>
256 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} 323 )}
257 className="franz-form__button--inverted" 324
258 onClick={openBilling} 325 <div className="account franz-form">
259 /> 326 <div className="account__box">
327 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2>
328 {!isDeleteAccountSuccessful && (
329 <div className="account__subscription">
330 <p>{intl.formatMessage(messages.deleteInfo)}</p>
260 <Button 331 <Button
261 label={intl.formatMessage(messages.invoicesButton)} 332 label={intl.formatMessage(messages.deleteAccount)}
262 className="franz-form__button--inverted" 333 buttonType="danger"
263 onClick={openInvoices} 334 onClick={() => deleteAccount()}
335 loaded={!isLoadingDeleteAccount}
264 /> 336 />
265 </div> 337 </div>
266 </div> 338 )}
267 </div> 339 {isDeleteAccountSuccessful && (
268 )} 340 <p>{intl.formatMessage(messages.deleteEmailSent)}</p>
269 {!user.isPremium && ( 341 )}
270 <div className="account franz-form">
271 <div className="account__box">
272 <SubscriptionForm
273 onCloseWindow={onCloseSubscriptionWindow}
274 />
275 </div>
276 </div> 342 </div>
277 )} 343 </div>
278 </> 344 </>
279 )} 345 )}
280
281 <div className="account franz-form">
282 <div className="account__box">
283 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2>
284 {!isDeleteAccountSuccessful && (
285 <div className="account__subscription">
286 <p>{intl.formatMessage(messages.deleteInfo)}</p>
287 <Button
288 label={intl.formatMessage(messages.deleteAccount)}
289 buttonType="danger"
290 onClick={() => deleteAccount()}
291 loaded={!isLoadingDeleteAccount}
292 />
293 </div>
294 )}
295 {isDeleteAccountSuccessful && (
296 <p>{intl.formatMessage(messages.deleteEmailSent)}</p>
297 )}
298 </div>
299 </div>
300 </> 346 </>
301 )} 347 )}
302 </div> 348 </div>
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 192cfde7a..eb3249fa0 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -119,6 +119,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
119 to="/settings/services" 119 to="/settings/services"
120 className="settings-navigation__link" 120 className="settings-navigation__link"
121 activeClassName="is-active" 121 activeClassName="is-active"
122 disabled={!isLoggedIn}
122 > 123 >
123 {intl.formatMessage(messages.yourServices)} 124 {intl.formatMessage(messages.yourServices)}
124 {' '} 125 {' '}
@@ -134,6 +135,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
134 to="/settings/workspaces" 135 to="/settings/workspaces"
135 className="settings-navigation__link" 136 className="settings-navigation__link"
136 activeClassName="is-active" 137 activeClassName="is-active"
138 disabled={!isLoggedIn}
137 > 139 >
138 {intl.formatMessage(messages.yourWorkspaces)} 140 {intl.formatMessage(messages.yourWorkspaces)}
139 {' '} 141 {' '}
@@ -148,6 +150,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
148 to="/settings/user" 150 to="/settings/user"
149 className="settings-navigation__link" 151 className="settings-navigation__link"
150 activeClassName="is-active" 152 activeClassName="is-active"
153 disabled={!isLoggedIn}
151 > 154 >
152 {intl.formatMessage(messages.account)} 155 {intl.formatMessage(messages.account)}
153 </Link> 156 </Link>
@@ -155,6 +158,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
155 to="/settings/team" 158 to="/settings/team"
156 className="settings-navigation__link" 159 className="settings-navigation__link"
157 activeClassName="is-active" 160 activeClassName="is-active"
161 disabled={!isLoggedIn}
158 > 162 >
159 {intl.formatMessage(messages.team)} 163 {intl.formatMessage(messages.team)}
160 {!user.data.isPremium && ( 164 {!user.data.isPremium && (
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index 877cbc588..d08e6cbc2 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -153,6 +153,8 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
153 const communityRecipes = recipes.filter(r => !r.isDevRecipe); 153 const communityRecipes = recipes.filter(r => !r.isDevRecipe);
154 const devRecipes = recipes.filter(r => r.isDevRecipe); 154 const devRecipes = recipes.filter(r => r.isDevRecipe);
155 155
156 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
157
156 return ( 158 return (
157 <div className="settings__main"> 159 <div className="settings__main">
158 <div className="settings__header"> 160 <div className="settings__header">
@@ -265,7 +267,7 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
265 <RecipeItem 267 <RecipeItem
266 key={recipe.id} 268 key={recipe.id}
267 recipe={recipe} 269 recipe={recipe}
268 onClick={() => showAddServiceInterface({ recipeId: recipe.id })} 270 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })}
269 /> 271 />
270 ))} 272 ))}
271 </div> 273 </div>
@@ -278,7 +280,7 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
278 <RecipeItem 280 <RecipeItem
279 key={recipe.id} 281 key={recipe.id}
280 recipe={recipe} 282 recipe={recipe}
281 onClick={() => showAddServiceInterface({ recipeId: recipe.id })} 283 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })}
282 /> 284 />
283 ))} 285 ))}
284 </div> 286 </div>
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 2be5c4ed7..f7868db50 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -12,6 +12,7 @@ import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
12import Input from '../../ui/Input'; 12import Input from '../../ui/Input';
13 13
14import { FRANZ_TRANSLATION } from '../../../config'; 14import { FRANZ_TRANSLATION } from '../../../config';
15import { isMac } from '../../../environment';
15 16
16function escapeHtml(unsafe) { 17function escapeHtml(unsafe) {
17 return unsafe 18 return unsafe
@@ -31,10 +32,18 @@ const messages = defineMessages({
31 id: 'settings.app.headlineGeneral', 32 id: 'settings.app.headlineGeneral',
32 defaultMessage: '!!!General', 33 defaultMessage: '!!!General',
33 }, 34 },
35 sentryInfo: {
36 id: 'settings.app.sentryInfo',
37 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.',
38 },
34 hibernateInfo: { 39 hibernateInfo: {
35 id: 'settings.app.hibernateInfo', 40 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.', 41 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 }, 42 },
43 inactivityLockInfo: {
44 id: 'settings.app.inactivityLockInfo',
45 defaultMessage: '!!!Minutes of inactivity, after which Ferdi should automatically lock. Use 0 to disable',
46 },
38 serverInfo: { 47 serverInfo: {
39 id: 'settings.app.serverInfo', 48 id: 'settings.app.serverInfo',
40 defaultMessage: '!!!We advice you to logout after changing your server as your settings might not be saved otherwise.', 49 defaultMessage: '!!!We advice you to logout after changing your server as your settings might not be saved otherwise.',
@@ -49,7 +58,7 @@ const messages = defineMessages({
49 }, 58 },
50 lockedPassword: { 59 lockedPassword: {
51 id: 'settings.app.lockedPassword', 60 id: 'settings.app.lockedPassword',
52 defaultMessage: '!!!Ferdi Lock Password', 61 defaultMessage: '!!!Password',
53 }, 62 },
54 lockedPasswordInfo: { 63 lockedPasswordInfo: {
55 id: 'settings.app.lockedPasswordInfo', 64 id: 'settings.app.lockedPasswordInfo',
@@ -57,7 +66,7 @@ const messages = defineMessages({
57 }, 66 },
58 lockInfo: { 67 lockInfo: {
59 id: 'settings.app.lockInfo', 68 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.', 69 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.',
61 }, 70 },
62 scheduledDNDTimeInfo: { 71 scheduledDNDTimeInfo: {
63 id: 'settings.app.scheduledDNDTimeInfo', 72 id: 'settings.app.scheduledDNDTimeInfo',
@@ -141,6 +150,10 @@ const messages = defineMessages({
141 }, 150 },
142}); 151});
143 152
153const Hr = () => (
154 <hr style={{ marginBottom: 20 }} />
155);
156
144export default @observer class EditSettingsForm extends Component { 157export default @observer class EditSettingsForm extends Component {
145 static propTypes = { 158 static propTypes = {
146 checkForUpdates: PropTypes.func.isRequired, 159 checkForUpdates: PropTypes.func.isRequired,
@@ -161,6 +174,8 @@ export default @observer class EditSettingsForm extends Component {
161 noUpdates: PropTypes.bool.isRequired, 174 noUpdates: PropTypes.bool.isRequired,
162 hibernationEnabled: PropTypes.bool.isRequired, 175 hibernationEnabled: PropTypes.bool.isRequired,
163 isDarkmodeEnabled: PropTypes.bool.isRequired, 176 isDarkmodeEnabled: PropTypes.bool.isRequired,
177 isTrayEnabled: PropTypes.bool.isRequired,
178 isAdaptableDarkModeEnabled: PropTypes.bool.isRequired,
164 openProcessManager: PropTypes.func.isRequired, 179 openProcessManager: PropTypes.func.isRequired,
165 }; 180 };
166 181
@@ -185,6 +200,7 @@ export default @observer class EditSettingsForm extends Component {
185 installUpdate, 200 installUpdate,
186 form, 201 form,
187 isCheckingForUpdates, 202 isCheckingForUpdates,
203 isAdaptableDarkModeEnabled,
188 isUpdateAvailable, 204 isUpdateAvailable,
189 noUpdateAvailable, 205 noUpdateAvailable,
190 updateIsReadyToInstall, 206 updateIsReadyToInstall,
@@ -198,6 +214,7 @@ export default @observer class EditSettingsForm extends Component {
198 noUpdates, 214 noUpdates,
199 hibernationEnabled, 215 hibernationEnabled,
200 isDarkmodeEnabled, 216 isDarkmodeEnabled,
217 isTrayEnabled,
201 openProcessManager, 218 openProcessManager,
202 } = this.props; 219 } = this.props;
203 const { intl } = this.context; 220 const { intl } = this.context;
@@ -234,8 +251,17 @@ export default @observer class EditSettingsForm extends Component {
234 <Toggle field={form.$('autoLaunchOnStart')} /> 251 <Toggle field={form.$('autoLaunchOnStart')} />
235 <Toggle field={form.$('runInBackground')} /> 252 <Toggle field={form.$('runInBackground')} />
236 <Toggle field={form.$('enableSystemTray')} /> 253 <Toggle field={form.$('enableSystemTray')} />
254 {isTrayEnabled && <Toggle field={form.$('startMinimized')} />}
237 <Toggle field={form.$('privateNotifications')} /> 255 <Toggle field={form.$('privateNotifications')} />
238 <Toggle field={form.$('showServiceNavigationBar')} /> 256 <Toggle field={form.$('showServiceNavigationBar')} />
257
258 <Hr />
259
260 <Toggle field={form.$('sentry')} />
261 <p>{intl.formatMessage(messages.sentryInfo)}</p>
262
263 <Hr />
264
239 <Toggle field={form.$('hibernate')} /> 265 <Toggle field={form.$('hibernate')} />
240 {hibernationEnabled && ( 266 {hibernationEnabled && (
241 <Select field={form.$('hibernationStrategy')} /> 267 <Select field={form.$('hibernationStrategy')} />
@@ -250,9 +276,13 @@ export default @observer class EditSettingsForm extends Component {
250 { intl.formatMessage(messages.hibernateInfo) } 276 { intl.formatMessage(messages.hibernateInfo) }
251 </span> 277 </span>
252 </p> 278 </p>
279
280 <Hr />
281
253 {process.platform === 'win32' && ( 282 {process.platform === 'win32' && (
254 <Toggle field={form.$('minimizeToSystemTray')} /> 283 <Toggle field={form.$('minimizeToSystemTray')} />
255 )} 284 )}
285
256 <Input 286 <Input
257 placeholder="Server" 287 placeholder="Server"
258 onChange={e => this.submit(e)} 288 onChange={e => this.submit(e)}
@@ -260,7 +290,14 @@ export default @observer class EditSettingsForm extends Component {
260 autoFocus 290 autoFocus
261 /> 291 />
262 {isLoggedIn && ( 292 {isLoggedIn && (
263 <p>{ intl.formatMessage(messages.serverInfo) }</p> 293 <p
294 className="settings__message"
295 style={{
296 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
297 }}
298 >
299 { intl.formatMessage(messages.serverInfo) }
300 </p>
264 )} 301 )}
265 {server === 'https://api.franzinfra.com' && ( 302 {server === 'https://api.franzinfra.com' && (
266 <p 303 <p
@@ -285,9 +322,16 @@ export default @observer class EditSettingsForm extends Component {
285 /> 322 />
286 </p> 323 </p>
287 )} 324 )}
325
326 <Hr />
327
288 {isWorkspaceEnabled && ( 328 {isWorkspaceEnabled && (
289 <Toggle field={form.$('keepAllWorkspacesLoaded')} /> 329 <Toggle field={form.$('keepAllWorkspacesLoaded')} />
290 )} 330 )}
331
332
333 <Hr />
334
291 {isTodosEnabled && ( 335 {isTodosEnabled && (
292 <> 336 <>
293 <Toggle field={form.$('enableTodos')} /> 337 <Toggle field={form.$('enableTodos')} />
@@ -296,10 +340,19 @@ export default @observer class EditSettingsForm extends Component {
296 onChange={e => this.submit(e)} 340 onChange={e => this.submit(e)}
297 field={form.$('todoServer')} 341 field={form.$('todoServer')}
298 /> 342 />
299 <p>{ intl.formatMessage(messages.todoServerInfo) }</p> 343 <p
344 className="settings__message"
345 style={{
346 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem',
347 }}
348 >
349 { intl.formatMessage(messages.todoServerInfo) }
350 </p>
300 </> 351 </>
301 )} 352 )}
302 353
354 <Hr />
355
303 <Toggle field={form.$('lockingFeatureEnabled')} /> 356 <Toggle field={form.$('lockingFeatureEnabled')} />
304 {lockingFeatureEnabled && ( 357 {lockingFeatureEnabled && (
305 <> 358 <>
@@ -314,6 +367,16 @@ export default @observer class EditSettingsForm extends Component {
314 <p> 367 <p>
315 { intl.formatMessage(messages.lockedPasswordInfo) } 368 { intl.formatMessage(messages.lockedPasswordInfo) }
316 </p> 369 </p>
370
371 <Input
372 placeholder="Lock after inactivity"
373 onChange={e => this.submit(e)}
374 field={form.$('inactivityLock')}
375 autoFocus
376 />
377 <p>
378 { intl.formatMessage(messages.inactivityLockInfo) }
379 </p>
317 </> 380 </>
318 )} 381 )}
319 <p 382 <p
@@ -327,6 +390,7 @@ export default @observer class EditSettingsForm extends Component {
327 </span> 390 </span>
328 </p> 391 </p>
329 392
393 <Hr />
330 394
331 <Toggle field={form.$('scheduledDNDEnabled')} /> 395 <Toggle field={form.$('scheduledDNDEnabled')} />
332 {scheduledDNDEnabled && ( 396 {scheduledDNDEnabled && (
@@ -382,8 +446,12 @@ export default @observer class EditSettingsForm extends Component {
382 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2> 446 <h2 id="apperance">{intl.formatMessage(messages.headlineAppearance)}</h2>
383 <Toggle field={form.$('showDisabledServices')} /> 447 <Toggle field={form.$('showDisabledServices')} />
384 <Toggle field={form.$('showMessageBadgeWhenMuted')} /> 448 <Toggle field={form.$('showMessageBadgeWhenMuted')} />
385 <Toggle field={form.$('darkMode')} /> 449
386 {isDarkmodeEnabled && ( 450 <Hr />
451
452 {isMac && <Toggle field={form.$('adaptableDarkMode')} />}
453 {!(isMac && isAdaptableDarkModeEnabled) && <Toggle field={form.$('darkMode')} disabled={isAdaptableDarkModeEnabled} />}
454 {(isDarkmodeEnabled || isAdaptableDarkModeEnabled) && (
387 <> 455 <>
388 <Toggle field={form.$('universalDarkMode')} /> 456 <Toggle field={form.$('universalDarkMode')} />
389 <p 457 <p
@@ -399,6 +467,8 @@ export default @observer class EditSettingsForm extends Component {
399 </> 467 </>
400 )} 468 )}
401 469
470 <Hr />
471
402 <Input 472 <Input
403 placeholder="Accent Color" 473 placeholder="Accent Color"
404 onChange={e => this.submit(e)} 474 onChange={e => this.submit(e)}
@@ -409,6 +479,9 @@ export default @observer class EditSettingsForm extends Component {
409 {/* Language */} 479 {/* Language */}
410 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> 480 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2>
411 <Select field={form.$('locale')} showLabel={false} /> 481 <Select field={form.$('locale')} showLabel={false} />
482
483 <Hr />
484
412 <PremiumFeatureContainer 485 <PremiumFeatureContainer
413 condition={!isSpellcheckerIncludedInCurrentPlan} 486 condition={!isSpellcheckerIncludedInCurrentPlan}
414 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 487 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
@@ -482,20 +555,17 @@ export default @observer class EditSettingsForm extends Component {
482 loaded={!isCheckingForUpdates || !isUpdateAvailable} 555 loaded={!isCheckingForUpdates || !isUpdateAvailable}
483 /> 556 />
484 )} 557 )}
485 {noUpdateAvailable && (
486 <p>{intl.formatMessage(messages.updateStatusUpToDate)}</p>
487 )}
488 <br /> 558 <br />
489 <Toggle field={form.$('beta')} /> 559 <Toggle field={form.$('beta')} />
490 <Toggle field={form.$('noUpdates')} /> 560 <Toggle field={form.$('noUpdates')} />
491 {intl.formatMessage(messages.currentVersion)} 561 {intl.formatMessage(messages.currentVersion)}
492 {' '} 562 {' '}
493 {remote.app.getVersion()} 563 {remote.app.getVersion()}
564 <br />
565 <br />
566 {noUpdateAvailable && intl.formatMessage(messages.updateStatusUpToDate)}
494 <p className="settings__message"> 567 <p className="settings__message">
495 <span className="mdi mdi-information" /> 568
496 {intl.formatMessage(messages.languageDisclaimer)}
497 </p>
498 <p className="settings__message">
499 <span className="mdi mdi-github-face" /> 569 <span className="mdi mdi-github-face" />
500 <span> 570 <span>
501 Ferdi is based on 571 Ferdi is based on
@@ -506,6 +576,9 @@ export default @observer class EditSettingsForm extends Component {
506 {' '} 576 {' '}
507 <a href="https://github.com/meetfranz/franz/blob/master/LICENSE" target="_blank">Apache-2.0 License</a> 577 <a href="https://github.com/meetfranz/franz/blob/master/LICENSE" target="_blank">Apache-2.0 License</a>
508 </span> 578 </span>
579 <br />
580 <span className="mdi mdi-information" />
581 {intl.formatMessage(messages.languageDisclaimer)}
509 </p> 582 </p>
510 </form> 583 </form>
511 </div> 584 </div>
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 7e6d93997..3d5358d89 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -210,7 +210,14 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
210 <h1 className={classes.headline}> 210 <h1 className={classes.headline}>
211 {intl.formatMessage(messages.teamsUnavailable)} 211 {intl.formatMessage(messages.teamsUnavailable)}
212 </h1> 212 </h1>
213 {intl.formatMessage(messages.teamsUnavailableInfo)} 213 <p
214 className="settings__message"
215 style={{
216 borderTop: 0, marginTop: 0,
217 }}
218 >
219 {intl.formatMessage(messages.teamsUnavailableInfo)}
220 </p>
214 </div> 221 </div>
215 </div> 222 </div>
216 ); 223 );
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js
index 83a05554b..e0b608b96 100644
--- a/src/components/ui/ImageUpload.js
+++ b/src/components/ui/ImageUpload.js
@@ -91,7 +91,7 @@ export default @observer class ImageUpload extends Component {
91 onDrop={this.onDrop.bind(this)} 91 onDrop={this.onDrop.bind(this)}
92 className={cssClasses} 92 className={cssClasses}
93 multiple={multiple} 93 multiple={multiple}
94 accept="image/jpeg, image/png" 94 accept="image/jpeg, image/png, image/svg+xml"
95 > 95 >
96 <i className="mdi mdi-file-image" /> 96 <i className="mdi mdi-file-image" />
97 <p> 97 <p>
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js
index 5f729844b..7930d98b4 100644
--- a/src/components/ui/Link.js
+++ b/src/components/ui/Link.js
@@ -11,7 +11,8 @@ import { matchRoute } from '../../helpers/routing-helpers';
11// TODO: create container component for this component 11// TODO: create container component for this component
12export default @inject('stores') @observer class Link extends Component { 12export default @inject('stores') @observer class Link extends Component {
13 onClick(e) { 13 onClick(e) {
14 if (this.props.target === '_blank') { 14 if (this.props.disabled) e.preventDefault();
15 else if (this.props.target === '_blank') {
15 e.preventDefault(); 16 e.preventDefault();
16 shell.openExternal(this.props.to); 17 shell.openExternal(this.props.to);
17 } 18 }
@@ -39,6 +40,7 @@ export default @inject('stores') @observer class Link extends Component {
39 const linkClasses = classnames({ 40 const linkClasses = classnames({
40 [`${className}`]: true, 41 [`${className}`]: true,
41 [`${activeClassName}`]: match, 42 [`${activeClassName}`]: match,
43 'is-disabled': this.props.disabled,
42 }); 44 });
43 45
44 return ( 46 return (
@@ -68,12 +70,14 @@ Link.wrappedComponent.propTypes = {
68 strictFilter: PropTypes.bool, 70 strictFilter: PropTypes.bool,
69 target: PropTypes.string, 71 target: PropTypes.string,
70 style: PropTypes.object, 72 style: PropTypes.object,
73 disabled: PropTypes.bool,
71}; 74};
72 75
73Link.wrappedComponent.defaultProps = { 76Link.wrappedComponent.defaultProps = {
74 className: '', 77 className: '',
75 activeClassName: '', 78 activeClassName: '',
76 strictFilter: false, 79 strictFilter: false,
80 disabled: false,
77 target: '', 81 target: '',
78 style: {}, 82 style: {},
79}; 83};
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js
index 923f10327..c58d69374 100644
--- a/src/components/ui/WebviewLoader/index.js
+++ b/src/components/ui/WebviewLoader/index.js
@@ -10,7 +10,7 @@ import styles from './styles';
10const messages = defineMessages({ 10const messages = defineMessages({
11 loading: { 11 loading: {
12 id: 'service.webviewLoader.loading', 12 id: 'service.webviewLoader.loading',
13 defaultMessage: '!!!Loading', 13 defaultMessage: '!!!Loading {service}',
14 }, 14 },
15}); 15});
16 16
@@ -30,7 +30,7 @@ export default @injectSheet(styles) @observer class WebviewLoader extends Compon
30 return ( 30 return (
31 <FullscreenLoader 31 <FullscreenLoader
32 className={classes.component} 32 className={classes.component}
33 title={`${intl.formatMessage(messages.loading)} ${name}`} 33 title={`${intl.formatMessage(messages.loading, { service: name })}`}
34 /> 34 />
35 ); 35 );
36 } 36 }