diff options
Diffstat (limited to 'src/components')
57 files changed, 566 insertions, 1780 deletions
diff --git a/src/components/TrialActivationInfoBar.js b/src/components/TrialActivationInfoBar.js deleted file mode 100644 index 77ab97565..000000000 --- a/src/components/TrialActivationInfoBar.js +++ /dev/null | |||
@@ -1,94 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { defineMessages, intlShape } from 'react-intl'; | ||
4 | import ms from 'ms'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | import classnames from 'classnames'; | ||
7 | |||
8 | import InfoBar from './ui/InfoBar'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | message: { | ||
12 | id: 'infobar.trialActivated', | ||
13 | defaultMessage: '!!!Your trial was successfully activated. Happy messaging!', | ||
14 | }, | ||
15 | }); | ||
16 | |||
17 | const styles = { | ||
18 | notification: { | ||
19 | height: 'auto', | ||
20 | position: 'absolute', | ||
21 | top: -50, | ||
22 | transition: 'top 0.3s', | ||
23 | zIndex: 500, | ||
24 | width: 'calc(100% - 300px)', | ||
25 | }, | ||
26 | show: { | ||
27 | top: 0, | ||
28 | }, | ||
29 | }; | ||
30 | |||
31 | @injectSheet(styles) | ||
32 | class TrialActivationInfoBar extends Component { | ||
33 | static propTypes = { | ||
34 | // eslint-disable-next-line | ||
35 | classes: PropTypes.object.isRequired, | ||
36 | }; | ||
37 | |||
38 | static contextTypes = { | ||
39 | intl: intlShape, | ||
40 | }; | ||
41 | |||
42 | state = { | ||
43 | showing: false, | ||
44 | removed: false, | ||
45 | } | ||
46 | |||
47 | componentDidMount() { | ||
48 | setTimeout(() => { | ||
49 | this.setState({ | ||
50 | showing: true, | ||
51 | }); | ||
52 | }, 0); | ||
53 | |||
54 | setTimeout(() => { | ||
55 | this.setState({ | ||
56 | showing: false, | ||
57 | }); | ||
58 | }, ms('6s')); | ||
59 | |||
60 | setTimeout(() => { | ||
61 | this.setState({ | ||
62 | removed: true, | ||
63 | }); | ||
64 | }, ms('7s')); | ||
65 | } | ||
66 | |||
67 | render() { | ||
68 | const { classes } = this.props; | ||
69 | const { showing, removed } = this.state; | ||
70 | const { intl } = this.context; | ||
71 | |||
72 | if (removed) return null; | ||
73 | |||
74 | return ( | ||
75 | <div | ||
76 | className={classnames({ | ||
77 | [classes.notification]: true, | ||
78 | [classes.show]: showing, | ||
79 | })} | ||
80 | > | ||
81 | <InfoBar | ||
82 | type="primary" | ||
83 | position="top" | ||
84 | sticky | ||
85 | > | ||
86 | <span className="mdi mdi-information" /> | ||
87 | {intl.formatMessage(messages.message)} | ||
88 | </InfoBar> | ||
89 | </div> | ||
90 | ); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | export default TrialActivationInfoBar; | ||
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js index 6fa3adf92..c0e6b982b 100644 --- a/src/components/auth/AuthLayout.js +++ b/src/components/auth/AuthLayout.js | |||
@@ -7,14 +7,19 @@ import { TitleBar } from 'electron-react-titlebar'; | |||
7 | import Link from '../ui/Link'; | 7 | import Link from '../ui/Link'; |
8 | import InfoBar from '../ui/InfoBar'; | 8 | import InfoBar from '../ui/InfoBar'; |
9 | 9 | ||
10 | import { oneOrManyChildElements, globalError as globalErrorPropType } from '../../prop-types'; | 10 | import { |
11 | oneOrManyChildElements, | ||
12 | globalError as globalErrorPropType, | ||
13 | } from '../../prop-types'; | ||
11 | import globalMessages from '../../i18n/globalMessages'; | 14 | import globalMessages from '../../i18n/globalMessages'; |
12 | 15 | ||
13 | import { isWindows } from '../../environment'; | 16 | import { isWindows } from '../../environment'; |
14 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; | 17 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; |
15 | import { GITHUB_FERDI_URL } from '../../config'; | 18 | import { GITHUB_FERDI_URL } from '../../config'; |
16 | 19 | ||
17 | export default @observer class AuthLayout extends Component { | 20 | export default |
21 | @observer | ||
22 | class AuthLayout extends Component { | ||
18 | static propTypes = { | 23 | static propTypes = { |
19 | children: oneOrManyChildElements.isRequired, | 24 | children: oneOrManyChildElements.isRequired, |
20 | error: globalErrorPropType.isRequired, | 25 | error: globalErrorPropType.isRequired, |
@@ -30,7 +35,7 @@ export default @observer class AuthLayout extends Component { | |||
30 | 35 | ||
31 | state = { | 36 | state = { |
32 | shouldShowAppUpdateInfoBar: true, | 37 | shouldShowAppUpdateInfoBar: true, |
33 | } | 38 | }; |
34 | 39 | ||
35 | static defaultProps = { | 40 | static defaultProps = { |
36 | nextAppReleaseVersion: null, | 41 | nextAppReleaseVersion: null, |
@@ -57,12 +62,15 @@ export default @observer class AuthLayout extends Component { | |||
57 | 62 | ||
58 | return ( | 63 | return ( |
59 | <> | 64 | <> |
60 | {isWindows && !isFullScreen && <TitleBar menu={window.ferdi.menu.template} icon="assets/images/logo.svg" />} | 65 | {isWindows && !isFullScreen && ( |
66 | <TitleBar | ||
67 | menu={window.ferdi.menu.template} | ||
68 | icon="assets/images/logo.svg" | ||
69 | /> | ||
70 | )} | ||
61 | <div className="auth"> | 71 | <div className="auth"> |
62 | {!isOnline && ( | 72 | {!isOnline && ( |
63 | <InfoBar | 73 | <InfoBar type="warning"> |
64 | type="warning" | ||
65 | > | ||
66 | <span className="mdi mdi-flash" /> | 74 | <span className="mdi mdi-flash" /> |
67 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} | 75 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} |
68 | </InfoBar> | 76 | </InfoBar> |
@@ -95,7 +103,11 @@ export default @observer class AuthLayout extends Component { | |||
95 | })} | 103 | })} |
96 | </div> | 104 | </div> |
97 | {/* </div> */} | 105 | {/* </div> */} |
98 | <Link to={`${GITHUB_FERDI_URL}/ferdi`} className="auth__adlk" target="_blank"> | 106 | <Link |
107 | to={`${GITHUB_FERDI_URL}/ferdi`} | ||
108 | className="auth__adlk" | ||
109 | target="_blank" | ||
110 | > | ||
99 | <img src="./assets/images/adlk.svg" alt="" /> | 111 | <img src="./assets/images/adlk.svg" alt="" /> |
100 | </Link> | 112 | </Link> |
101 | </div> | 113 | </div> |
diff --git a/src/components/auth/ChangeServer.js b/src/components/auth/ChangeServer.js index 0dedd825a..7bab80280 100644 --- a/src/components/auth/ChangeServer.js +++ b/src/components/auth/ChangeServer.js | |||
@@ -100,7 +100,7 @@ export default @observer class ChangeServer extends Component { | |||
100 | const { intl } = this.context; | 100 | const { intl } = this.context; |
101 | return ( | 101 | return ( |
102 | <div className="auth__container"> | 102 | <div className="auth__container"> |
103 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 103 | <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> |
104 | <h1>{intl.formatMessage(messages.headline)}</h1> | 104 | <h1>{intl.formatMessage(messages.headline)}</h1> |
105 | {form.$('server').value === this.franzServer | 105 | {form.$('server').value === this.franzServer |
106 | && ( | 106 | && ( |
@@ -113,7 +113,7 @@ export default @observer class ChangeServer extends Component { | |||
113 | && ( | 113 | && ( |
114 | <Input | 114 | <Input |
115 | placeholder="Custom Server" | 115 | placeholder="Custom Server" |
116 | onChange={e => this.submit(e)} | 116 | onChange={(e) => this.submit(e)} |
117 | field={form.$('customServer')} | 117 | field={form.$('customServer')} |
118 | /> | 118 | /> |
119 | )} | 119 | )} |
diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.js index 1fdbea595..2ad8a2409 100644 --- a/src/components/auth/Locked.js +++ b/src/components/auth/Locked.js | |||
@@ -102,7 +102,7 @@ export default @observer class Locked extends Component { | |||
102 | 102 | ||
103 | return ( | 103 | return ( |
104 | <div className="auth__container"> | 104 | <div className="auth__container"> |
105 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 105 | <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> |
106 | <img | 106 | <img |
107 | src="./assets/images/logo.svg" | 107 | src="./assets/images/logo.svg" |
108 | className="auth__logo" | 108 | className="auth__logo" |
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index 52b09eab6..9e6a8d046 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js | |||
@@ -135,7 +135,7 @@ export default @inject('actions') @observer class Login extends Component { | |||
135 | 135 | ||
136 | return ( | 136 | return ( |
137 | <div className="auth__container"> | 137 | <div className="auth__container"> |
138 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 138 | <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> |
139 | <img | 139 | <img |
140 | src="./assets/images/logo.svg" | 140 | src="./assets/images/logo.svg" |
141 | className="auth__logo" | 141 | className="auth__logo" |
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.js index ad34e39af..eafc48502 100644 --- a/src/components/auth/Password.js +++ b/src/components/auth/Password.js | |||
@@ -86,7 +86,7 @@ export default @observer class Password extends Component { | |||
86 | 86 | ||
87 | return ( | 87 | return ( |
88 | <div className="auth__container"> | 88 | <div className="auth__container"> |
89 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 89 | <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> |
90 | <img | 90 | <img |
91 | src="./assets/images/logo.svg" | 91 | src="./assets/images/logo.svg" |
92 | className="auth__logo" | 92 | className="auth__logo" |
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js deleted file mode 100644 index 2fcabe54d..000000000 --- a/src/components/auth/Pricing.js +++ /dev/null | |||
@@ -1,270 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | import { H2, Loader } from '@meetfranz/ui'; | ||
7 | import classnames from 'classnames'; | ||
8 | |||
9 | import { Button } from '@meetfranz/forms'; | ||
10 | import { FeatureItem } from '../ui/FeatureItem'; | ||
11 | import { FeatureList } from '../ui/FeatureList'; | ||
12 | |||
13 | const messages = defineMessages({ | ||
14 | headline: { | ||
15 | id: 'pricing.trial.headline.pro', | ||
16 | defaultMessage: '!!!Hi {name}, welcome to Franz', | ||
17 | }, | ||
18 | specialTreat: { | ||
19 | id: 'pricing.trial.intro.specialTreat', | ||
20 | defaultMessage: '!!!We have a special treat for you.', | ||
21 | }, | ||
22 | tryPro: { | ||
23 | id: 'pricing.trial.intro.tryPro', | ||
24 | defaultMessage: '!!!Enjoy the full Franz Professional experience completely free for 14 days.', | ||
25 | }, | ||
26 | happyMessaging: { | ||
27 | id: 'pricing.trial.intro.happyMessaging', | ||
28 | defaultMessage: '!!!Happy messaging,', | ||
29 | }, | ||
30 | noStringsAttachedHeadline: { | ||
31 | id: 'pricing.trial.terms.headline', | ||
32 | defaultMessage: '!!!No strings attached', | ||
33 | }, | ||
34 | noCreditCard: { | ||
35 | id: 'pricing.trial.terms.noCreditCard', | ||
36 | defaultMessage: '!!!No credit card required', | ||
37 | }, | ||
38 | automaticTrialEnd: { | ||
39 | id: 'pricing.trial.terms.automaticTrialEnd', | ||
40 | defaultMessage: '!!!Your free trial ends automatically after 14 days', | ||
41 | }, | ||
42 | trialWorth: { | ||
43 | id: 'pricing.trial.terms.trialWorth', | ||
44 | defaultMessage: '!!!Free trial (normally {currency}{price} per month)', | ||
45 | }, | ||
46 | activationError: { | ||
47 | id: 'pricing.trial.error', | ||
48 | defaultMessage: '!!!Sorry, we could not activate your trial!', | ||
49 | }, | ||
50 | ctaAccept: { | ||
51 | id: 'pricing.trial.cta.accept', | ||
52 | defaultMessage: '!!!Start my 14-day Franz Professional Trial ', | ||
53 | }, | ||
54 | ctaStart: { | ||
55 | id: 'pricing.trial.cta.start', | ||
56 | defaultMessage: '!!!Start using Franz', | ||
57 | }, | ||
58 | ctaSkip: { | ||
59 | id: 'pricing.trial.cta.skip', | ||
60 | defaultMessage: '!!!Continue to Ferdi', | ||
61 | }, | ||
62 | featuresHeadline: { | ||
63 | id: 'pricing.trial.features.headline', | ||
64 | defaultMessage: '!!!Franz Professional includes:', | ||
65 | }, | ||
66 | }); | ||
67 | |||
68 | const styles = theme => ({ | ||
69 | root: { | ||
70 | width: '500px !important', | ||
71 | textAlign: 'center', | ||
72 | padding: 20, | ||
73 | zIndex: 100, | ||
74 | |||
75 | '& h1': { | ||
76 | }, | ||
77 | }, | ||
78 | container: { | ||
79 | position: 'relative', | ||
80 | marginLeft: -150, | ||
81 | }, | ||
82 | welcomeOffer: { | ||
83 | textAlign: 'center', | ||
84 | fontWeight: 'bold', | ||
85 | marginBottom: '6 !important', | ||
86 | }, | ||
87 | keyTerms: { | ||
88 | textAlign: 'center', | ||
89 | }, | ||
90 | content: { | ||
91 | position: 'relative', | ||
92 | zIndex: 20, | ||
93 | }, | ||
94 | featureContainer: { | ||
95 | width: 300, | ||
96 | position: 'absolute', | ||
97 | left: 'calc(100% / 2 + 250px)', | ||
98 | marginTop: 20, | ||
99 | background: theme.signup.pricing.feature.background, | ||
100 | height: 'auto', | ||
101 | padding: 20, | ||
102 | borderTopRightRadius: theme.borderRadius, | ||
103 | borderBottomRightRadius: theme.borderRadius, | ||
104 | zIndex: 10, | ||
105 | }, | ||
106 | featureItem: { | ||
107 | borderBottom: [1, 'solid', theme.signup.pricing.feature.border], | ||
108 | }, | ||
109 | cta: { | ||
110 | marginTop: 40, | ||
111 | width: '100%', | ||
112 | }, | ||
113 | skipLink: { | ||
114 | textAlign: 'center', | ||
115 | marginTop: 10, | ||
116 | }, | ||
117 | error: { | ||
118 | margin: [20, 0, 0], | ||
119 | color: theme.styleTypes.danger.accent, | ||
120 | }, | ||
121 | priceContainer: { | ||
122 | display: 'flex', | ||
123 | justifyContent: 'space-evenly', | ||
124 | margin: [10, 0, 15], | ||
125 | }, | ||
126 | price: { | ||
127 | '& sup': { | ||
128 | verticalAlign: 14, | ||
129 | fontSize: 20, | ||
130 | }, | ||
131 | }, | ||
132 | figure: { | ||
133 | fontSize: 40, | ||
134 | }, | ||
135 | regularPrice: { | ||
136 | position: 'relative', | ||
137 | |||
138 | '&:before': { | ||
139 | content: '" "', | ||
140 | position: 'absolute', | ||
141 | width: '130%', | ||
142 | height: 1, | ||
143 | top: 14, | ||
144 | left: -12, | ||
145 | borderBottom: [3, 'solid', 'red'], | ||
146 | transform: 'rotateZ(-20deg)', | ||
147 | }, | ||
148 | }, | ||
149 | }); | ||
150 | |||
151 | export default @injectSheet(styles) @observer class Signup extends Component { | ||
152 | static propTypes = { | ||
153 | onSubmit: PropTypes.func.isRequired, | ||
154 | isLoadingRequiredData: PropTypes.bool.isRequired, | ||
155 | isActivatingTrial: PropTypes.bool.isRequired, | ||
156 | trialActivationError: PropTypes.bool.isRequired, | ||
157 | canSkipTrial: PropTypes.bool.isRequired, | ||
158 | classes: PropTypes.object.isRequired, | ||
159 | currency: PropTypes.string.isRequired, | ||
160 | price: PropTypes.number.isRequired, | ||
161 | name: PropTypes.string.isRequired, | ||
162 | }; | ||
163 | |||
164 | static contextTypes = { | ||
165 | intl: intlShape, | ||
166 | }; | ||
167 | |||
168 | render() { | ||
169 | const { | ||
170 | onSubmit, | ||
171 | isLoadingRequiredData, | ||
172 | isActivatingTrial, | ||
173 | trialActivationError, | ||
174 | canSkipTrial, | ||
175 | classes, | ||
176 | currency, | ||
177 | price, | ||
178 | name, | ||
179 | } = this.props; | ||
180 | const { intl } = this.context; | ||
181 | |||
182 | const [intPart, fractionPart] = (price).toString().split('.'); | ||
183 | |||
184 | return ( | ||
185 | <> | ||
186 | <div className={classnames('auth__container', classes.root, classes.container)}> | ||
187 | <form className="franz-form auth__form"> | ||
188 | {isLoadingRequiredData ? <Loader /> : ( | ||
189 | <img | ||
190 | src="./assets/images/sm.png" | ||
191 | className="auth__logo auth__logo--sm" | ||
192 | alt="" | ||
193 | /> | ||
194 | )} | ||
195 | <h1>{intl.formatMessage(messages.headline, { name })}</h1> | ||
196 | <div className="auth__letter"> | ||
197 | <p> | ||
198 | {intl.formatMessage(messages.specialTreat)} | ||
199 | <br /> | ||
200 | </p> | ||
201 | <p> | ||
202 | {intl.formatMessage(messages.tryPro)} | ||
203 | <br /> | ||
204 | </p> | ||
205 | <p> | ||
206 | {intl.formatMessage(messages.happyMessaging)} | ||
207 | </p> | ||
208 | <p> | ||
209 | <strong>Stefan Malzner</strong> | ||
210 | </p> | ||
211 | </div> | ||
212 | <div className={classes.priceContainer}> | ||
213 | <p className={classnames(classes.price, classes.regularPrice)}> | ||
214 | <span className={classes.figure}> | ||
215 | {currency} | ||
216 | {intPart} | ||
217 | </span> | ||
218 | <sup>{fractionPart}</sup> | ||
219 | </p> | ||
220 | <p className={classnames(classes.price, classes.trialPrice)}> | ||
221 | <span className={classes.figure}> | ||
222 | {currency} | ||
223 | 0 | ||
224 | </span> | ||
225 | <sup>00</sup> | ||
226 | </p> | ||
227 | </div> | ||
228 | <div className={classes.keyTerms}> | ||
229 | <H2> | ||
230 | {intl.formatMessage(messages.noStringsAttachedHeadline)} | ||
231 | </H2> | ||
232 | <ul className={classes.keyTermsList}> | ||
233 | <FeatureItem | ||
234 | icon="👉" | ||
235 | name={intl.formatMessage(messages.trialWorth, { | ||
236 | currency, | ||
237 | price, | ||
238 | })} | ||
239 | /> | ||
240 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> | ||
241 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> | ||
242 | </ul> | ||
243 | </div> | ||
244 | {trialActivationError && ( | ||
245 | <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p> | ||
246 | )} | ||
247 | <Button | ||
248 | label={intl.formatMessage(!canSkipTrial ? messages.ctaStart : messages.ctaAccept)} | ||
249 | className={classes.cta} | ||
250 | onClick={onSubmit} | ||
251 | busy={isActivatingTrial} | ||
252 | disabled={isLoadingRequiredData || isActivatingTrial} | ||
253 | /> | ||
254 | {canSkipTrial && ( | ||
255 | <p className={classes.skipLink}> | ||
256 | <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a> | ||
257 | </p> | ||
258 | )} | ||
259 | </form> | ||
260 | </div> | ||
261 | <div className={classes.featureContainer}> | ||
262 | <H2> | ||
263 | {intl.formatMessage(messages.featuresHeadline)} | ||
264 | </H2> | ||
265 | <FeatureList /> | ||
266 | </div> | ||
267 | </> | ||
268 | ); | ||
269 | } | ||
270 | } | ||
diff --git a/src/components/auth/SetupAssistant.js b/src/components/auth/SetupAssistant.js index e03cf9101..0f821d4fc 100644 --- a/src/components/auth/SetupAssistant.js +++ b/src/components/auth/SetupAssistant.js | |||
@@ -18,15 +18,16 @@ const SLACK_ID = 'slack'; | |||
18 | const messages = defineMessages({ | 18 | const messages = defineMessages({ |
19 | headline: { | 19 | headline: { |
20 | id: 'setupAssistant.headline', | 20 | id: 'setupAssistant.headline', |
21 | defaultMessage: '!!!Let\'s get started', | 21 | defaultMessage: "!!!Let's get started", |
22 | }, | 22 | }, |
23 | subHeadline: { | 23 | subHeadline: { |
24 | id: 'setupAssistant.subheadline', | 24 | id: 'setupAssistant.subheadline', |
25 | defaultMessage: '!!!Choose from our most used services and get back on top of your messaging now.', | 25 | defaultMessage: |
26 | '!!!Choose from our most used services and get back on top of your messaging now.', | ||
26 | }, | 27 | }, |
27 | submitButtonLabel: { | 28 | submitButtonLabel: { |
28 | id: 'setupAssistant.submit.label', | 29 | id: 'setupAssistant.submit.label', |
29 | defaultMessage: '!!!Let\'s go', | 30 | defaultMessage: "!!!Let's go", |
30 | }, | 31 | }, |
31 | inviteSuccessInfo: { | 32 | inviteSuccessInfo: { |
32 | id: 'invite.successInfo', | 33 | id: 'invite.successInfo', |
@@ -34,14 +35,19 @@ const messages = defineMessages({ | |||
34 | }, | 35 | }, |
35 | }); | 36 | }); |
36 | 37 | ||
38 | let transition = 'none'; | ||
39 | |||
40 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
41 | transition = 'all 0.25s'; | ||
42 | } | ||
43 | |||
37 | const styles = theme => ({ | 44 | const styles = theme => ({ |
38 | root: { | 45 | root: { |
39 | width: '500px !important', | 46 | width: '500px !important', |
40 | textAlign: 'center', | 47 | textAlign: 'center', |
41 | padding: 20, | 48 | padding: 20, |
42 | 49 | ||
43 | '& h1': { | 50 | '& h1': {}, |
44 | }, | ||
45 | }, | 51 | }, |
46 | servicesGrid: { | 52 | servicesGrid: { |
47 | display: 'flex', | 53 | display: 'flex', |
@@ -60,7 +66,7 @@ const styles = theme => ({ | |||
60 | borderRadius: theme.borderRadius, | 66 | borderRadius: theme.borderRadius, |
61 | marginBottom: 10, | 67 | marginBottom: 10, |
62 | opacity: 0.5, | 68 | opacity: 0.5, |
63 | transition: 'all 0.25s', | 69 | transition, |
64 | border: [3, 'solid', 'transparent'], | 70 | border: [3, 'solid', 'transparent'], |
65 | 71 | ||
66 | '& h2': { | 72 | '& h2': { |
@@ -70,10 +76,8 @@ const styles = theme => ({ | |||
70 | 76 | ||
71 | '&:hover': { | 77 | '&:hover': { |
72 | border: [3, 'solid', theme.brandPrimary], | 78 | border: [3, 'solid', theme.brandPrimary], |
73 | '& $serviceIcon': { | 79 | '& $serviceIcon': {}, |
74 | }, | ||
75 | }, | 80 | }, |
76 | |||
77 | }, | 81 | }, |
78 | selected: { | 82 | selected: { |
79 | border: [3, 'solid', theme.brandPrimary], | 83 | border: [3, 'solid', theme.brandPrimary], |
@@ -82,7 +86,7 @@ const styles = theme => ({ | |||
82 | }, | 86 | }, |
83 | serviceIcon: { | 87 | serviceIcon: { |
84 | width: 50, | 88 | width: 50, |
85 | transition: 'all 0.25s', | 89 | transition, |
86 | }, | 90 | }, |
87 | 91 | ||
88 | slackModalContent: { | 92 | slackModalContent: { |
@@ -125,7 +129,8 @@ const styles = theme => ({ | |||
125 | }, | 129 | }, |
126 | }); | 130 | }); |
127 | 131 | ||
128 | @injectSheet(styles) @observer | 132 | @injectSheet(styles) |
133 | @observer | ||
129 | class SetupAssistant extends Component { | 134 | class SetupAssistant extends Component { |
130 | static propTypes = { | 135 | static propTypes = { |
131 | classes: PropTypes.object.isRequired, | 136 | classes: PropTypes.object.isRequired, |
@@ -144,13 +149,17 @@ class SetupAssistant extends Component { | |||
144 | }; | 149 | }; |
145 | 150 | ||
146 | state = { | 151 | state = { |
147 | services: [{ | 152 | services: [ |
148 | id: 'whatsapp', | 153 | { |
149 | }, { | 154 | id: 'whatsapp', |
150 | id: 'messenger', | 155 | }, |
151 | }, { | 156 | { |
152 | id: 'gmail', | 157 | id: 'messenger', |
153 | }], | 158 | }, |
159 | { | ||
160 | id: 'gmail', | ||
161 | }, | ||
162 | ], | ||
154 | isSlackModalOpen: false, | 163 | isSlackModalOpen: false, |
155 | slackWorkspace: '', | 164 | slackWorkspace: '', |
156 | }; | 165 | }; |
@@ -158,7 +167,9 @@ class SetupAssistant extends Component { | |||
158 | slackWorkspaceHandler() { | 167 | slackWorkspaceHandler() { |
159 | const { slackWorkspace = '', services } = this.state; | 168 | const { slackWorkspace = '', services } = this.state; |
160 | 169 | ||
161 | const sanitizedWorkspace = slackWorkspace.trim().replace(/^https?:\/\//, ''); | 170 | const sanitizedWorkspace = slackWorkspace |
171 | .trim() | ||
172 | .replace(/^https?:\/\//, ''); | ||
162 | 173 | ||
163 | if (sanitizedWorkspace) { | 174 | if (sanitizedWorkspace) { |
164 | const index = services.findIndex(s => s.id === SLACK_ID); | 175 | const index = services.findIndex(s => s.id === SLACK_ID); |
@@ -179,9 +190,17 @@ class SetupAssistant extends Component { | |||
179 | render() { | 190 | render() { |
180 | const { intl } = this.context; | 191 | const { intl } = this.context; |
181 | const { | 192 | const { |
182 | classes, isInviteSuccessful, onSubmit, services, isSettingUpServices, | 193 | classes, |
194 | isInviteSuccessful, | ||
195 | onSubmit, | ||
196 | services, | ||
197 | isSettingUpServices, | ||
183 | } = this.props; | 198 | } = this.props; |
184 | const { isSlackModalOpen, slackWorkspace, services: addedServices } = this.state; | 199 | const { |
200 | isSlackModalOpen, | ||
201 | slackWorkspace, | ||
202 | services: addedServices, | ||
203 | } = this.state; | ||
185 | 204 | ||
186 | return ( | 205 | return ( |
187 | <div className={`auth__container ${classes.root}`}> | 206 | <div className={`auth__container ${classes.root}`}> |
@@ -197,25 +216,18 @@ class SetupAssistant extends Component { | |||
197 | </Appear> | 216 | </Appear> |
198 | )} | 217 | )} |
199 | 218 | ||
200 | <img | 219 | <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> |
201 | src="./assets/images/logo.svg" | 220 | <h1>{intl.formatMessage(messages.headline)}</h1> |
202 | className="auth__logo" | 221 | <h2>{intl.formatMessage(messages.subHeadline)}</h2> |
203 | alt="" | ||
204 | /> | ||
205 | <h1> | ||
206 | {intl.formatMessage(messages.headline)} | ||
207 | </h1> | ||
208 | <h2> | ||
209 | {intl.formatMessage(messages.subHeadline)} | ||
210 | </h2> | ||
211 | <div className={classnames('grid', classes.servicesGrid)}> | 222 | <div className={classnames('grid', classes.servicesGrid)}> |
212 | {Object.keys(services).map((id) => { | 223 | {Object.keys(services).map(id => { |
213 | const service = services[id]; | 224 | const service = services[id]; |
214 | return ( | 225 | return ( |
215 | <button | 226 | <button |
216 | className={classnames({ | 227 | className={classnames({ |
217 | [classes.serviceContainer]: true, | 228 | [classes.serviceContainer]: true, |
218 | [classes.selected]: this.state.services.findIndex(s => s.id === id) !== -1, | 229 | [classes.selected]: |
230 | this.state.services.findIndex(s => s.id === id) !== -1, | ||
219 | })} | 231 | })} |
220 | key={id} | 232 | key={id} |
221 | onClick={() => { | 233 | onClick={() => { |
@@ -244,9 +256,7 @@ class SetupAssistant extends Component { | |||
244 | className={classes.serviceIcon} | 256 | className={classes.serviceIcon} |
245 | alt="" | 257 | alt="" |
246 | /> | 258 | /> |
247 | <h2> | 259 | <h2>{service.name}</h2> |
248 | {service.name} | ||
249 | </h2> | ||
250 | {id === SLACK_ID && slackWorkspace && ( | 260 | {id === SLACK_ID && slackWorkspace && ( |
251 | <Badge type="secondary" className={classes.slackBadge}> | 261 | <Badge type="secondary" className={classes.slackBadge}> |
252 | {slackWorkspace} | 262 | {slackWorkspace} |
@@ -275,22 +285,22 @@ class SetupAssistant extends Component { | |||
275 | <div className={classes.slackModalContent}> | 285 | <div className={classes.slackModalContent}> |
276 | <img src={`${CDN_URL}/recipes/dist/slack/src/icon.svg`} alt="" /> | 286 | <img src={`${CDN_URL}/recipes/dist/slack/src/icon.svg`} alt="" /> |
277 | <h1>Create your first Slack workspace</h1> | 287 | <h1>Create your first Slack workspace</h1> |
278 | <form onSubmit={(e) => { | 288 | <form |
279 | e.preventDefault(); | 289 | onSubmit={e => { |
280 | this.slackWorkspaceHandler(); | 290 | e.preventDefault(); |
281 | }} | 291 | this.slackWorkspaceHandler(); |
292 | }} | ||
282 | > | 293 | > |
283 | <Input | 294 | <Input |
284 | suffix=".slack.com" | 295 | suffix=".slack.com" |
285 | placeholder="workspace-url" | 296 | placeholder="workspace-url" |
286 | onChange={e => this.setState({ slackWorkspace: e.target.value })} | 297 | onChange={e => |
298 | this.setState({ slackWorkspace: e.target.value }) | ||
299 | } | ||
287 | value={slackWorkspace} | 300 | value={slackWorkspace} |
288 | /> | 301 | /> |
289 | <div className={classes.modalActionContainer}> | 302 | <div className={classes.modalActionContainer}> |
290 | <Button | 303 | <Button type="submit" label="Save" /> |
291 | type="submit" | ||
292 | label="Save" | ||
293 | /> | ||
294 | <Button | 304 | <Button |
295 | type="link" | 305 | type="link" |
296 | buttonType="secondary" | 306 | buttonType="secondary" |
@@ -305,7 +315,7 @@ class SetupAssistant extends Component { | |||
305 | <Button | 315 | <Button |
306 | type="button" | 316 | type="button" |
307 | className="auth__button" | 317 | className="auth__button" |
308 | // disabled={!atLeastOneEmailAddress} | 318 | // disabled={!atLeastOneEmailAddress} |
309 | label={intl.formatMessage(messages.submitButtonLabel)} | 319 | label={intl.formatMessage(messages.submitButtonLabel)} |
310 | onClick={() => onSubmit(this.state.services)} | 320 | onClick={() => onSubmit(this.state.services)} |
311 | busy={isSettingUpServices} | 321 | busy={isSettingUpServices} |
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js index 140867ea1..6fb41a164 100644 --- a/src/components/auth/Signup.js +++ b/src/components/auth/Signup.js | |||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; | |||
4 | import { observer, inject } from 'mobx-react'; | 4 | import { observer, inject } from 'mobx-react'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | 6 | ||
7 | import { isDevMode, termsBase, useLiveAPI } from '../../environment'; | 7 | import { isDevMode, useLiveAPI } from '../../environment'; |
8 | import Form from '../../lib/Form'; | 8 | import Form from '../../lib/Form'; |
9 | import { required, email, minLength } from '../../helpers/validation-helpers'; | 9 | import { required, email, minLength } from '../../helpers/validation-helpers'; |
10 | import serverlessLogin from '../../helpers/serverless-helpers'; | 10 | import serverlessLogin from '../../helpers/serverless-helpers'; |
@@ -14,6 +14,7 @@ import Link from '../ui/Link'; | |||
14 | import Infobox from '../ui/Infobox'; | 14 | import Infobox from '../ui/Infobox'; |
15 | 15 | ||
16 | import { globalError as globalErrorPropType } from '../../prop-types'; | 16 | import { globalError as globalErrorPropType } from '../../prop-types'; |
17 | import { termsBase } from '../../api/apiBase'; | ||
17 | 18 | ||
18 | const messages = defineMessages({ | 19 | const messages = defineMessages({ |
19 | headline: { | 20 | headline: { |
@@ -138,7 +139,7 @@ export default @inject('actions') @observer class Signup extends Component { | |||
138 | return ( | 139 | return ( |
139 | <div className="auth__scroll-container"> | 140 | <div className="auth__scroll-container"> |
140 | <div className="auth__container auth__container--signup"> | 141 | <div className="auth__container auth__container--signup"> |
141 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 142 | <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> |
142 | <img | 143 | <img |
143 | src="./assets/images/logo.svg" | 144 | src="./assets/images/logo.svg" |
144 | className="auth__logo" | 145 | className="auth__logo" |
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js index 5f2fac64b..cb522e26e 100644 --- a/src/components/auth/Welcome.js +++ b/src/components/auth/Welcome.js | |||
@@ -84,7 +84,7 @@ export default @inject('actions') @observer class Login extends Component { | |||
84 | </Link> | 84 | </Link> |
85 | </div> | 85 | </div> |
86 | <div className="welcome__featured-services"> | 86 | <div className="welcome__featured-services"> |
87 | {recipes.map(recipe => ( | 87 | {recipes.map((recipe) => ( |
88 | <div | 88 | <div |
89 | key={recipe.id} | 89 | key={recipe.id} |
90 | className="welcome__featured-service" | 90 | className="welcome__featured-service" |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index a60270a6f..d5e1deb39 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -19,10 +19,7 @@ import { isWindows } from '../../environment'; | |||
19 | import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; | 19 | import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; |
20 | import { workspaceStore } from '../../features/workspaces'; | 20 | import { workspaceStore } from '../../features/workspaces'; |
21 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; | 21 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; |
22 | import TrialActivationInfoBar from '../TrialActivationInfoBar'; | ||
23 | import Todos from '../../features/todos/containers/TodosScreen'; | 22 | import Todos from '../../features/todos/containers/TodosScreen'; |
24 | import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen'; | ||
25 | import TrialStatusBar from '../../features/trialStatusBar/containers/TrialStatusBarScreen'; | ||
26 | 23 | ||
27 | function createMarkup(HTMLString) { | 24 | function createMarkup(HTMLString) { |
28 | return { __html: HTMLString }; | 25 | return { __html: HTMLString }; |
@@ -43,22 +40,32 @@ const messages = defineMessages({ | |||
43 | }, | 40 | }, |
44 | authRequestFailed: { | 41 | authRequestFailed: { |
45 | id: 'infobar.authRequestFailed', | 42 | id: 'infobar.authRequestFailed', |
46 | defaultMessage: '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.', | 43 | defaultMessage: |
44 | '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.', | ||
47 | }, | 45 | }, |
48 | }); | 46 | }); |
49 | 47 | ||
48 | let transition = 'none'; | ||
49 | |||
50 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
51 | transition = 'transform 0.5s ease'; | ||
52 | } | ||
53 | |||
50 | const styles = theme => ({ | 54 | const styles = theme => ({ |
51 | appContent: { | 55 | appContent: { |
52 | // width: `calc(100% + ${theme.workspaces.drawer.width}px)`, | 56 | // width: `calc(100% + ${theme.workspaces.drawer.width}px)`, |
53 | width: '100%', | 57 | width: '100%', |
54 | transition: 'transform 0.5s ease', | 58 | transition, |
55 | transform() { | 59 | transform() { |
56 | return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`; | 60 | return workspaceStore.isWorkspaceDrawerOpen |
61 | ? 'translateX(0)' | ||
62 | : `translateX(-${theme.workspaces.drawer.width}px)`; | ||
57 | }, | 63 | }, |
58 | }, | 64 | }, |
59 | }); | 65 | }); |
60 | 66 | ||
61 | @injectSheet(styles) @observer | 67 | @injectSheet(styles) |
68 | @observer | ||
62 | class AppLayout extends Component { | 69 | class AppLayout extends Component { |
63 | static propTypes = { | 70 | static propTypes = { |
64 | classes: PropTypes.object.isRequired, | 71 | classes: PropTypes.object.isRequired, |
@@ -79,12 +86,11 @@ class AppLayout extends Component { | |||
79 | areRequiredRequestsSuccessful: PropTypes.bool.isRequired, | 86 | areRequiredRequestsSuccessful: PropTypes.bool.isRequired, |
80 | retryRequiredRequests: PropTypes.func.isRequired, | 87 | retryRequiredRequests: PropTypes.func.isRequired, |
81 | areRequiredRequestsLoading: PropTypes.bool.isRequired, | 88 | areRequiredRequestsLoading: PropTypes.bool.isRequired, |
82 | hasActivatedTrial: PropTypes.bool.isRequired, | ||
83 | }; | 89 | }; |
84 | 90 | ||
85 | state = { | 91 | state = { |
86 | shouldShowAppUpdateInfoBar: true, | 92 | shouldShowAppUpdateInfoBar: true, |
87 | } | 93 | }; |
88 | 94 | ||
89 | static defaultProps = { | 95 | static defaultProps = { |
90 | children: [], | 96 | children: [], |
@@ -115,7 +121,6 @@ class AppLayout extends Component { | |||
115 | areRequiredRequestsSuccessful, | 121 | areRequiredRequestsSuccessful, |
116 | retryRequiredRequests, | 122 | retryRequiredRequests, |
117 | areRequiredRequestsLoading, | 123 | areRequiredRequestsLoading, |
118 | hasActivatedTrial, | ||
119 | } = this.props; | 124 | } = this.props; |
120 | 125 | ||
121 | const { intl } = this.context; | 126 | const { intl } = this.context; |
@@ -123,45 +128,48 @@ class AppLayout extends Component { | |||
123 | return ( | 128 | return ( |
124 | <ErrorBoundary> | 129 | <ErrorBoundary> |
125 | <div className="app"> | 130 | <div className="app"> |
126 | {isWindows && !isFullScreen && <TitleBar menu={window.ferdi.menu.template} icon="assets/images/logo.svg" />} | 131 | {isWindows && !isFullScreen && ( |
132 | <TitleBar | ||
133 | menu={window.ferdi.menu.template} | ||
134 | icon="assets/images/logo.svg" | ||
135 | /> | ||
136 | )} | ||
127 | <div className={`app__content ${classes.appContent}`}> | 137 | <div className={`app__content ${classes.appContent}`}> |
128 | {workspacesDrawer} | 138 | {workspacesDrawer} |
129 | {sidebar} | 139 | {sidebar} |
130 | <div className="app__service"> | 140 | <div className="app__service"> |
131 | <WorkspaceSwitchingIndicator /> | 141 | <WorkspaceSwitchingIndicator /> |
132 | {news.length > 0 && news.map(item => ( | 142 | {news.length > 0 && |
143 | news.map(item => ( | ||
144 | <InfoBar | ||
145 | key={item.id} | ||
146 | position="top" | ||
147 | type={item.type} | ||
148 | sticky={item.sticky} | ||
149 | onHide={() => removeNewsItem({ newsId: item.id })} | ||
150 | > | ||
151 | <span | ||
152 | dangerouslySetInnerHTML={createMarkup(item.message)} | ||
153 | onClick={event => { | ||
154 | const { target } = event; | ||
155 | if (target && target.hasAttribute('data-is-news-cta')) { | ||
156 | removeNewsItem({ newsId: item.id }); | ||
157 | } | ||
158 | }} | ||
159 | /> | ||
160 | </InfoBar> | ||
161 | ))} | ||
162 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( | ||
133 | <InfoBar | 163 | <InfoBar |
134 | key={item.id} | 164 | type="danger" |
135 | position="top" | 165 | ctaLabel="Try again" |
136 | type={item.type} | 166 | ctaLoading={areRequiredRequestsLoading} |
137 | sticky={item.sticky} | 167 | sticky |
138 | onHide={() => removeNewsItem({ newsId: item.id })} | 168 | onClick={retryRequiredRequests} |
139 | > | 169 | > |
140 | <span | 170 | <span className="mdi mdi-flash" /> |
141 | dangerouslySetInnerHTML={createMarkup(item.message)} | 171 | {intl.formatMessage(messages.requiredRequestsFailed)} |
142 | onClick={(event) => { | ||
143 | const { target } = event; | ||
144 | if (target && target.hasAttribute('data-is-news-cta')) { | ||
145 | removeNewsItem({ newsId: item.id }); | ||
146 | } | ||
147 | }} | ||
148 | /> | ||
149 | </InfoBar> | 172 | </InfoBar> |
150 | ))} | ||
151 | {hasActivatedTrial && ( | ||
152 | <TrialActivationInfoBar /> | ||
153 | )} | ||
154 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( | ||
155 | <InfoBar | ||
156 | type="danger" | ||
157 | ctaLabel="Try again" | ||
158 | ctaLoading={areRequiredRequestsLoading} | ||
159 | sticky | ||
160 | onClick={retryRequiredRequests} | ||
161 | > | ||
162 | <span className="mdi mdi-flash" /> | ||
163 | {intl.formatMessage(messages.requiredRequestsFailed)} | ||
164 | </InfoBar> | ||
165 | )} | 173 | )} |
166 | {authRequestFailed && ( | 174 | {authRequestFailed && ( |
167 | <InfoBar | 175 | <InfoBar |
@@ -186,7 +194,7 @@ class AppLayout extends Component { | |||
186 | {intl.formatMessage(messages.servicesUpdated)} | 194 | {intl.formatMessage(messages.servicesUpdated)} |
187 | </InfoBar> | 195 | </InfoBar> |
188 | )} | 196 | )} |
189 | { appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( | 197 | {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( |
190 | <AppUpdateInfoBar | 198 | <AppUpdateInfoBar |
191 | nextAppReleaseVersion={nextAppReleaseVersion} | 199 | nextAppReleaseVersion={nextAppReleaseVersion} |
192 | onInstallUpdate={installAppUpdate} | 200 | onInstallUpdate={installAppUpdate} |
@@ -202,11 +210,9 @@ class AppLayout extends Component { | |||
202 | <PublishDebugInfo /> | 210 | <PublishDebugInfo /> |
203 | {services} | 211 | {services} |
204 | {children} | 212 | {children} |
205 | <TrialStatusBar /> | ||
206 | </div> | 213 | </div> |
207 | <Todos /> | 214 | <Todos /> |
208 | </div> | 215 | </div> |
209 | <PlanSelection /> | ||
210 | </div> | 216 | </div> |
211 | </ErrorBoundary> | 217 | </ErrorBoundary> |
212 | ); | 218 | ); |
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 802538eba..daa5642c3 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -6,16 +6,13 @@ import { inject, observer } from 'mobx-react'; | |||
6 | import { Link } from 'react-router'; | 6 | import { Link } from 'react-router'; |
7 | 7 | ||
8 | import Tabbar from '../services/tabs/Tabbar'; | 8 | import Tabbar from '../services/tabs/Tabbar'; |
9 | import { ctrlKey, isMac } from '../../environment'; | 9 | import { settingsShortcutKey, lockFerdiShortcutKey, todosToggleShortcutKey, workspaceToggleShortcutKey, addNewServiceShortcutKey, muteFerdiShortcutKey } from '../../environment'; |
10 | import { workspaceStore } from '../../features/workspaces'; | 10 | import { workspaceStore } from '../../features/workspaces'; |
11 | import { todosStore } from '../../features/todos'; | 11 | import { todosStore } from '../../features/todos'; |
12 | import { todoActions } from '../../features/todos/actions'; | 12 | import { todoActions } from '../../features/todos/actions'; |
13 | import AppStore from '../../stores/AppStore'; | 13 | import AppStore from '../../stores/AppStore'; |
14 | import SettingsStore from '../../stores/SettingsStore'; | 14 | import SettingsStore from '../../stores/SettingsStore'; |
15 | 15 | ||
16 | // Platform specific shortcut keys | ||
17 | const settingsShortcutKey = isMac ? ',' : 'P'; | ||
18 | |||
19 | const messages = defineMessages({ | 16 | const messages = defineMessages({ |
20 | settings: { | 17 | settings: { |
21 | id: 'sidebar.settings', | 18 | id: 'sidebar.settings', |
@@ -58,6 +55,19 @@ const messages = defineMessages({ | |||
58 | export default @inject('stores', 'actions') @observer class Sidebar extends Component { | 55 | export default @inject('stores', 'actions') @observer class Sidebar extends Component { |
59 | static propTypes = { | 56 | static propTypes = { |
60 | openSettings: PropTypes.func.isRequired, | 57 | openSettings: PropTypes.func.isRequired, |
58 | closeSettings: PropTypes.func.isRequired, | ||
59 | setActive: PropTypes.func.isRequired, | ||
60 | reorder: PropTypes.func.isRequired, | ||
61 | reload: PropTypes.func.isRequired, | ||
62 | toggleNotifications: PropTypes.func.isRequired, | ||
63 | toggleAudio: PropTypes.func.isRequired, | ||
64 | toggleDarkMode: PropTypes.func.isRequired, | ||
65 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | ||
66 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | ||
67 | deleteService: PropTypes.func.isRequired, | ||
68 | updateService: PropTypes.func.isRequired, | ||
69 | hibernateService: PropTypes.func.isRequired, | ||
70 | wakeUpService: PropTypes.func.isRequired, | ||
61 | toggleMuteApp: PropTypes.func.isRequired, | 71 | toggleMuteApp: PropTypes.func.isRequired, |
62 | isAppMuted: PropTypes.bool.isRequired, | 72 | isAppMuted: PropTypes.bool.isRequired, |
63 | isWorkspaceDrawerOpen: PropTypes.bool.isRequired, | 73 | isWorkspaceDrawerOpen: PropTypes.bool.isRequired, |
@@ -139,7 +149,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
139 | }, | 149 | }, |
140 | }); | 150 | }); |
141 | }} | 151 | }} |
142 | data-tip={`${intl.formatMessage(messages.lockFerdi)} (${ctrlKey}+Shift+L)`} | 152 | data-tip={`${intl.formatMessage(messages.lockFerdi)} (${lockFerdiShortcutKey(false)})`} |
143 | > | 153 | > |
144 | <i className="mdi mdi-lock" /> | 154 | <i className="mdi mdi-lock" /> |
145 | </button> | 155 | </button> |
@@ -152,8 +162,8 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
152 | this.updateToolTip(); | 162 | this.updateToolTip(); |
153 | }} | 163 | }} |
154 | disabled={isTodosServiceActive} | 164 | disabled={isTodosServiceActive} |
155 | className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} | 165 | className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} |
156 | data-tip={`${intl.formatMessage(todosToggleMessage)} (${ctrlKey}+T)`} | 166 | data-tip={`${intl.formatMessage(todosToggleMessage)} (${todosToggleShortcutKey(false)})`} |
157 | > | 167 | > |
158 | <i className="mdi mdi-check-all" /> | 168 | <i className="mdi mdi-check-all" /> |
159 | </button> | 169 | </button> |
@@ -166,7 +176,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
166 | this.updateToolTip(); | 176 | this.updateToolTip(); |
167 | }} | 177 | }} |
168 | className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`} | 178 | className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`} |
169 | data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${ctrlKey}+D)`} | 179 | data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${workspaceToggleShortcutKey(false)})`} |
170 | > | 180 | > |
171 | <i className="mdi mdi-view-grid" /> | 181 | <i className="mdi mdi-view-grid" /> |
172 | </button> | 182 | </button> |
@@ -178,7 +188,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
178 | this.updateToolTip(); | 188 | this.updateToolTip(); |
179 | }} | 189 | }} |
180 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} | 190 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} |
181 | data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} | 191 | data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${muteFerdiShortcutKey(false)})`} |
182 | > | 192 | > |
183 | <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> | 193 | <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> |
184 | </button> | 194 | </button> |
@@ -186,7 +196,7 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
186 | type="button" | 196 | type="button" |
187 | onClick={() => openSettings({ path: 'recipes' })} | 197 | onClick={() => openSettings({ path: 'recipes' })} |
188 | className="sidebar__button sidebar__button--new-service" | 198 | className="sidebar__button sidebar__button--new-service" |
189 | data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`} | 199 | data-tip={`${intl.formatMessage(messages.addNewService)} (${addNewServiceShortcutKey(false)})`} |
190 | > | 200 | > |
191 | <i className="mdi mdi-plus-box" /> | 201 | <i className="mdi mdi-plus-box" /> |
192 | </button> | 202 | </button> |
@@ -204,9 +214,10 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp | |||
204 | type="button" | 214 | type="button" |
205 | onClick={() => openSettings({ path: 'app' })} | 215 | onClick={() => openSettings({ path: 'app' })} |
206 | className="sidebar__button sidebar__button--settings" | 216 | className="sidebar__button sidebar__button--settings" |
207 | data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+${settingsShortcutKey})`} | 217 | data-tip={`${intl.formatMessage(messages.settings)} (${settingsShortcutKey(false)})`} |
208 | > | 218 | > |
209 | <i className="mdi mdi-settings" /> | 219 | {/* TODO: Because of https://github.com/Templarian/MaterialDesign-Webfont/issues/81 bug in @mdi/font in 5.9.55, added `mdi-memory` as a fallback */} |
220 | <i className="mdi mdi-settings mdi-memory" /> | ||
210 | { (this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.AVAILABLE | 221 | { (this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.AVAILABLE |
211 | || this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.DOWNLOADED) && ( | 222 | || this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.DOWNLOADED) && ( |
212 | <span className="update-available"> | 223 | <span className="update-available"> |
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js index e54a88faa..ebe863333 100644 --- a/src/components/services/content/ConnectionLostBanner.js +++ b/src/components/services/content/ConnectionLostBanner.js | |||
@@ -5,9 +5,7 @@ import injectSheet from 'react-jss'; | |||
5 | import { Icon } from '@meetfranz/ui'; | 5 | import { Icon } from '@meetfranz/ui'; |
6 | import { intlShape, defineMessages } from 'react-intl'; | 6 | import { intlShape, defineMessages } from 'react-intl'; |
7 | 7 | ||
8 | import { | 8 | import { mdiAlert } from '@mdi/js'; |
9 | mdiAlert, | ||
10 | } from '@mdi/js'; | ||
11 | import { LIVE_API_FERDI_WEBSITE } from '../../../config'; | 9 | import { LIVE_API_FERDI_WEBSITE } from '../../../config'; |
12 | // import { Button } from '@meetfranz/forms'; | 10 | // import { Button } from '@meetfranz/forms'; |
13 | 11 | ||
@@ -26,6 +24,12 @@ const messages = defineMessages({ | |||
26 | }, | 24 | }, |
27 | }); | 25 | }); |
28 | 26 | ||
27 | let buttonTransition = 'none'; | ||
28 | |||
29 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
30 | buttonTransition = 'opacity 0.25s'; | ||
31 | } | ||
32 | |||
29 | const styles = theme => ({ | 33 | const styles = theme => ({ |
30 | root: { | 34 | root: { |
31 | background: theme.colorBackground, | 35 | background: theme.colorBackground, |
@@ -47,7 +51,7 @@ const styles = theme => ({ | |||
47 | opacity: 0.7, | 51 | opacity: 0.7, |
48 | }, | 52 | }, |
49 | button: { | 53 | button: { |
50 | transition: 'opacity 0.25s', | 54 | transition: buttonTransition, |
51 | color: theme.colorText, | 55 | color: theme.colorText, |
52 | border: [1, 'solid', theme.colorText], | 56 | border: [1, 'solid', theme.colorText], |
53 | borderRadius: theme.borderRadiusSmall, | 57 | borderRadius: theme.borderRadiusSmall, |
@@ -65,13 +69,14 @@ const styles = theme => ({ | |||
65 | }, | 69 | }, |
66 | }); | 70 | }); |
67 | 71 | ||
68 | @injectSheet(styles) @observer | 72 | @injectSheet(styles) |
73 | @observer | ||
69 | class ConnectionLostBanner extends Component { | 74 | class ConnectionLostBanner extends Component { |
70 | static propTypes = { | 75 | static propTypes = { |
71 | classes: PropTypes.object.isRequired, | 76 | classes: PropTypes.object.isRequired, |
72 | name: PropTypes.string.isRequired, | 77 | name: PropTypes.string.isRequired, |
73 | reload: PropTypes.func.isRequired, | 78 | reload: PropTypes.func.isRequired, |
74 | } | 79 | }; |
75 | 80 | ||
76 | static contextTypes = { | 81 | static contextTypes = { |
77 | intl: intlShape, | 82 | intl: intlShape, |
@@ -80,20 +85,13 @@ class ConnectionLostBanner extends Component { | |||
80 | inputRef = React.createRef(); | 85 | inputRef = React.createRef(); |
81 | 86 | ||
82 | render() { | 87 | render() { |
83 | const { | 88 | const { classes, name, reload } = this.props; |
84 | classes, | ||
85 | name, | ||
86 | reload, | ||
87 | } = this.props; | ||
88 | 89 | ||
89 | const { intl } = this.context; | 90 | const { intl } = this.context; |
90 | 91 | ||
91 | return ( | 92 | return ( |
92 | <div className={classes.root}> | 93 | <div className={classes.root}> |
93 | <Icon | 94 | <Icon icon={mdiAlert} className={classes.icon} /> |
94 | icon={mdiAlert} | ||
95 | className={classes.icon} | ||
96 | /> | ||
97 | <p> | 95 | <p> |
98 | {intl.formatMessage(messages.text, { name })} | 96 | {intl.formatMessage(messages.text, { name })} |
99 | <br /> | 97 | <br /> |
@@ -104,11 +102,7 @@ class ConnectionLostBanner extends Component { | |||
104 | {intl.formatMessage(messages.moreInformation)} | 102 | {intl.formatMessage(messages.moreInformation)} |
105 | </a> | 103 | </a> |
106 | </p> | 104 | </p> |
107 | <button | 105 | <button type="button" className={classes.button} onClick={reload}> |
108 | type="button" | ||
109 | className={classes.button} | ||
110 | onClick={reload} | ||
111 | > | ||
112 | {intl.formatMessage(messages.cta)} | 106 | {intl.formatMessage(messages.cta)} |
113 | </button> | 107 | </button> |
114 | </div> | 108 | </div> |
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js index 9e2509ee5..72d62f5e3 100644 --- a/src/components/services/content/ErrorHandlers/styles.js +++ b/src/components/services/content/ErrorHandlers/styles.js | |||
@@ -1,4 +1,4 @@ | |||
1 | export default theme => ({ | 1 | export default (theme) => ({ |
2 | component: { | 2 | component: { |
3 | left: 0, | 3 | left: 0, |
4 | position: 'absolute', | 4 | position: 'absolute', |
diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js deleted file mode 100644 index 4b8d926aa..000000000 --- a/src/components/services/content/ServiceRestricted.js +++ /dev/null | |||
@@ -1,78 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import { serviceLimitStore } from '../../../features/serviceLimit'; | ||
7 | import Button from '../../ui/Button'; | ||
8 | import { RESTRICTION_TYPES } from '../../../models/Service'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | headlineServiceLimit: { | ||
12 | id: 'service.restrictedHandler.serviceLimit.headline', | ||
13 | defaultMessage: '!!!You have reached your service limit.', | ||
14 | }, | ||
15 | textServiceLimit: { | ||
16 | id: 'service.restrictedHandler.serviceLimit.text', | ||
17 | defaultMessage: '!!!Please upgrade your account to use more than {count} services.', | ||
18 | }, | ||
19 | headlineCustomUrl: { | ||
20 | id: 'service.restrictedHandler.customUrl.headline', | ||
21 | defaultMessage: '!!!Franz Professional Plan required', | ||
22 | }, | ||
23 | textCustomUrl: { | ||
24 | id: 'service.restrictedHandler.customUrl.text', | ||
25 | defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.', | ||
26 | }, | ||
27 | action: { | ||
28 | id: 'service.restrictedHandler.action', | ||
29 | defaultMessage: '!!!Upgrade Account', | ||
30 | }, | ||
31 | }); | ||
32 | |||
33 | export default @observer class ServiceRestricted extends Component { | ||
34 | static propTypes = { | ||
35 | name: PropTypes.string.isRequired, | ||
36 | upgrade: PropTypes.func.isRequired, | ||
37 | type: PropTypes.number.isRequired, | ||
38 | }; | ||
39 | |||
40 | static contextTypes = { | ||
41 | intl: intlShape, | ||
42 | }; | ||
43 | |||
44 | countdownInterval = null; | ||
45 | |||
46 | countdownIntervalTimeout = 1000; | ||
47 | |||
48 | render() { | ||
49 | const { | ||
50 | name, | ||
51 | upgrade, | ||
52 | type, | ||
53 | } = this.props; | ||
54 | const { intl } = this.context; | ||
55 | |||
56 | return ( | ||
57 | <div className="services__info-layer"> | ||
58 | {type === RESTRICTION_TYPES.SERVICE_LIMIT && ( | ||
59 | <> | ||
60 | <h1>{intl.formatMessage(messages.headlineServiceLimit)}</h1> | ||
61 | <p>{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}</p> | ||
62 | </> | ||
63 | )} | ||
64 | {type === RESTRICTION_TYPES.CUSTOM_URL && ( | ||
65 | <> | ||
66 | <h1>{intl.formatMessage(messages.headlineCustomUrl)}</h1> | ||
67 | <p>{intl.formatMessage(messages.textCustomUrl)}</p> | ||
68 | </> | ||
69 | )} | ||
70 | <Button | ||
71 | label={intl.formatMessage(messages.action, { name })} | ||
72 | buttonType="inverted" | ||
73 | onClick={() => upgrade()} | ||
74 | /> | ||
75 | </div> | ||
76 | ); | ||
77 | } | ||
78 | } | ||
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index 17d2db5a0..3fc084ff0 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js | |||
@@ -145,19 +145,17 @@ export default @inject('stores', 'actions') @observer class ServiceView extends | |||
145 | </> | 145 | </> |
146 | ) : ( | 146 | ) : ( |
147 | <> | 147 | <> |
148 | {(!service.isHibernating || service.isHibernationEnabled) ? ( | 148 | {!service.isHibernating ? ( |
149 | <> | 149 | <> |
150 | {showNavBar && ( | 150 | {showNavBar && ( |
151 | <WebControlsScreen service={service} /> | 151 | <WebControlsScreen service={service} /> |
152 | )} | 152 | )} |
153 | {!service.isHibernating && ( | 153 | <ServiceWebview |
154 | <ServiceWebview | 154 | service={service} |
155 | service={service} | 155 | setWebviewReference={setWebviewReference} |
156 | setWebviewReference={setWebviewReference} | 156 | detachService={detachService} |
157 | detachService={detachService} | 157 | isSpellcheckerEnabled={isSpellcheckerEnabled} |
158 | isSpellcheckerEnabled={isSpellcheckerEnabled} | 158 | /> |
159 | /> | ||
160 | )} | ||
161 | </> | 159 | </> |
162 | ) : ( | 160 | ) : ( |
163 | <div> | 161 | <div> |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 9e5fed996..3b499a5db 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { observable, reaction } from 'mobx'; | 4 | import { observable, reaction } from 'mobx'; |
5 | import ElectronWebView from 'react-electron-web-view'; | 5 | import ElectronWebView from 'react-electron-web-view'; |
6 | import path from 'path'; | 6 | import { join } from 'path'; |
7 | 7 | ||
8 | import ServiceModel from '../../../models/Service'; | 8 | import ServiceModel from '../../../models/Service'; |
9 | 9 | ||
@@ -59,7 +59,7 @@ class ServiceWebview extends Component { | |||
59 | isSpellcheckerEnabled, | 59 | isSpellcheckerEnabled, |
60 | } = this.props; | 60 | } = this.props; |
61 | 61 | ||
62 | const preloadScript = path.join(__dirname, '../../../', 'webview', 'recipe.js'); | 62 | const preloadScript = join(__dirname, '..', '..', '..', 'webview', 'recipe.js'); |
63 | 63 | ||
64 | return ( | 64 | return ( |
65 | <ElectronWebView | 65 | <ElectronWebView |
@@ -83,7 +83,7 @@ class ServiceWebview extends Component { | |||
83 | useragent={service.userAgent} | 83 | useragent={service.userAgent} |
84 | disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} | 84 | disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} |
85 | allowpopups | 85 | allowpopups |
86 | webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}, contextIsolation=false`} | 86 | webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`} |
87 | /> | 87 | /> |
88 | ); | 88 | ); |
89 | } | 89 | } |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index caa3cf9aa..bb93ff7d4 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -30,7 +30,7 @@ const messages = defineMessages({ | |||
30 | }, | 30 | }, |
31 | serverInfo: { | 31 | serverInfo: { |
32 | id: 'services.serverInfo', | 32 | id: 'services.serverInfo', |
33 | defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.', | 33 | defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!', |
34 | }, | 34 | }, |
35 | }); | 35 | }); |
36 | 36 | ||
@@ -54,7 +54,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
54 | openSettings: PropTypes.func.isRequired, | 54 | openSettings: PropTypes.func.isRequired, |
55 | update: PropTypes.func.isRequired, | 55 | update: PropTypes.func.isRequired, |
56 | userHasCompletedSignup: PropTypes.bool.isRequired, | 56 | userHasCompletedSignup: PropTypes.bool.isRequired, |
57 | hasActivatedTrial: PropTypes.bool.isRequired, | ||
58 | classes: PropTypes.object.isRequired, | 57 | classes: PropTypes.object.isRequired, |
59 | actions: PropTypes.object.isRequired, | 58 | actions: PropTypes.object.isRequired, |
60 | isSpellcheckerEnabled: PropTypes.bool.isRequired, | 59 | isSpellcheckerEnabled: PropTypes.bool.isRequired, |
@@ -109,7 +108,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
109 | openSettings, | 108 | openSettings, |
110 | update, | 109 | update, |
111 | userHasCompletedSignup, | 110 | userHasCompletedSignup, |
112 | hasActivatedTrial, | ||
113 | classes, | 111 | classes, |
114 | isSpellcheckerEnabled, | 112 | isSpellcheckerEnabled, |
115 | } = this.props; | 113 | } = this.props; |
@@ -123,7 +121,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
123 | 121 | ||
124 | return ( | 122 | return ( |
125 | <div className="services"> | 123 | <div className="services"> |
126 | {(userHasCompletedSignup || hasActivatedTrial) && ( | 124 | {userHasCompletedSignup && ( |
127 | <div className={classes.confettiContainer}> | 125 | <div className={classes.confettiContainer}> |
128 | <Confetti | 126 | <Confetti |
129 | width={window.width} | 127 | width={window.width} |
@@ -169,7 +167,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
169 | </div> | 167 | </div> |
170 | </Appear> | 168 | </Appear> |
171 | )} | 169 | )} |
172 | {services.filter(service => !service.isTodosService).map(service => ( | 170 | {services.filter((service) => !service.isTodosService).map((service) => ( |
173 | <ServiceView | 171 | <ServiceView |
174 | key={service.id} | 172 | key={service.id} |
175 | service={service} | 173 | service={service} |
@@ -186,7 +184,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services | |||
186 | }, | 184 | }, |
187 | redirect: false, | 185 | redirect: false, |
188 | })} | 186 | })} |
189 | upgrade={() => openSettings({ path: 'user' })} | ||
190 | isSpellcheckerEnabled={isSpellcheckerEnabled} | 187 | isSpellcheckerEnabled={isSpellcheckerEnabled} |
191 | /> | 188 | /> |
192 | ))} | 189 | ))} |
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js index b62940c06..10ff0bbbb 100644 --- a/src/components/services/content/WebviewCrashHandler.js +++ b/src/components/services/content/WebviewCrashHandler.js | |||
@@ -47,7 +47,7 @@ export default @observer class WebviewCrashHandler extends Component { | |||
47 | const { reload } = this.props; | 47 | const { reload } = this.props; |
48 | 48 | ||
49 | this.countdownInterval = setInterval(() => { | 49 | this.countdownInterval = setInterval(() => { |
50 | this.setState(prevState => ({ | 50 | this.setState((prevState) => ({ |
51 | countdown: prevState.countdown - this.countdownIntervalTimeout, | 51 | countdown: prevState.countdown - this.countdownIntervalTimeout, |
52 | })); | 52 | })); |
53 | 53 | ||
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js index 489027d57..1a389991d 100644 --- a/src/components/services/tabs/TabBarSortableList.js +++ b/src/components/services/tabs/TabBarSortableList.js | |||
@@ -14,9 +14,12 @@ class TabBarSortableList extends Component { | |||
14 | reload: PropTypes.func.isRequired, | 14 | reload: PropTypes.func.isRequired, |
15 | toggleNotifications: PropTypes.func.isRequired, | 15 | toggleNotifications: PropTypes.func.isRequired, |
16 | toggleAudio: PropTypes.func.isRequired, | 16 | toggleAudio: PropTypes.func.isRequired, |
17 | toggleDarkMode: PropTypes.func.isRequired, | ||
17 | deleteService: PropTypes.func.isRequired, | 18 | deleteService: PropTypes.func.isRequired, |
18 | disableService: PropTypes.func.isRequired, | 19 | disableService: PropTypes.func.isRequired, |
19 | enableService: PropTypes.func.isRequired, | 20 | enableService: PropTypes.func.isRequired, |
21 | hibernateService: PropTypes.func.isRequired, | ||
22 | wakeUpService: PropTypes.func.isRequired, | ||
20 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | 23 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, |
21 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | 24 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, |
22 | } | 25 | } |
@@ -28,9 +31,12 @@ class TabBarSortableList extends Component { | |||
28 | reload, | 31 | reload, |
29 | toggleNotifications, | 32 | toggleNotifications, |
30 | toggleAudio, | 33 | toggleAudio, |
34 | toggleDarkMode, | ||
31 | deleteService, | 35 | deleteService, |
32 | disableService, | 36 | disableService, |
33 | enableService, | 37 | enableService, |
38 | hibernateService, | ||
39 | wakeUpService, | ||
34 | openSettings, | 40 | openSettings, |
35 | showMessageBadgeWhenMutedSetting, | 41 | showMessageBadgeWhenMutedSetting, |
36 | showMessageBadgesEvenWhenMuted, | 42 | showMessageBadgesEvenWhenMuted, |
@@ -50,23 +56,17 @@ class TabBarSortableList extends Component { | |||
50 | reload={() => reload({ serviceId: service.id })} | 56 | reload={() => reload({ serviceId: service.id })} |
51 | toggleNotifications={() => toggleNotifications({ serviceId: service.id })} | 57 | toggleNotifications={() => toggleNotifications({ serviceId: service.id })} |
52 | toggleAudio={() => toggleAudio({ serviceId: service.id })} | 58 | toggleAudio={() => toggleAudio({ serviceId: service.id })} |
59 | toggleDarkMode={() => toggleDarkMode({ serviceId: service.id })} | ||
53 | deleteService={() => deleteService({ serviceId: service.id })} | 60 | deleteService={() => deleteService({ serviceId: service.id })} |
54 | disableService={() => disableService({ serviceId: service.id })} | 61 | disableService={() => disableService({ serviceId: service.id })} |
55 | enableService={() => enableService({ serviceId: service.id })} | 62 | enableService={() => enableService({ serviceId: service.id })} |
63 | hibernateService={() => hibernateService({ serviceId: service.id })} | ||
64 | wakeUpService={() => wakeUpService({ serviceId: service.id })} | ||
56 | openSettings={openSettings} | 65 | openSettings={openSettings} |
57 | showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} | 66 | showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} |
58 | showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} | 67 | showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} |
59 | /> | 68 | /> |
60 | ))} | 69 | ))} |
61 | {/* <li> | ||
62 | <button | ||
63 | className="sidebar__add-service" | ||
64 | onClick={() => openSettings({ path: 'recipes' })} | ||
65 | data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`} | ||
66 | > | ||
67 | <span className="mdi mdi-plus" /> | ||
68 | </button> | ||
69 | </li> */} | ||
70 | </ul> | 70 | </ul> |
71 | ); | 71 | ); |
72 | } | 72 | } |
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js index 5c3149a11..6a6d2c8c5 100644 --- a/src/components/services/tabs/TabItem.js +++ b/src/components/services/tabs/TabItem.js | |||
@@ -1,6 +1,4 @@ | |||
1 | import { | 1 | import { Menu, dialog, app, getCurrentWindow } from '@electron/remote'; |
2 | Menu, dialog, app, getCurrentWindow, | ||
3 | } from '@electron/remote'; | ||
4 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import PropTypes from 'prop-types'; | 4 | import PropTypes from 'prop-types'; |
@@ -12,9 +10,11 @@ import ms from 'ms'; | |||
12 | 10 | ||
13 | import { observable, autorun } from 'mobx'; | 11 | import { observable, autorun } from 'mobx'; |
14 | import ServiceModel from '../../../models/Service'; | 12 | import ServiceModel from '../../../models/Service'; |
15 | import { ctrlKey, cmdKey } from '../../../environment'; | 13 | import { shortcutKey } from '../../../environment'; |
16 | 14 | ||
17 | const IS_SERVICE_DEBUGGING_ENABLED = (localStorage.getItem('debug') || '').includes('Ferdi:Service'); | 15 | const IS_SERVICE_DEBUGGING_ENABLED = ( |
16 | localStorage.getItem('debug') || '' | ||
17 | ).includes('Ferdi:Service'); | ||
18 | 18 | ||
19 | const messages = defineMessages({ | 19 | const messages = defineMessages({ |
20 | reload: { | 20 | reload: { |
@@ -41,6 +41,14 @@ const messages = defineMessages({ | |||
41 | id: 'tabs.item.enableAudio', | 41 | id: 'tabs.item.enableAudio', |
42 | defaultMessage: '!!!Enable audio', | 42 | defaultMessage: '!!!Enable audio', |
43 | }, | 43 | }, |
44 | enableDarkMode: { | ||
45 | id: 'tabs.item.enableDarkMode', | ||
46 | defaultMessage: '!!!Enable Dark mode', | ||
47 | }, | ||
48 | disableDarkMode: { | ||
49 | id: 'tabs.item.disableDarkMode', | ||
50 | defaultMessage: '!!!Disable Dark mode', | ||
51 | }, | ||
44 | disableService: { | 52 | disableService: { |
45 | id: 'tabs.item.disableService', | 53 | id: 'tabs.item.disableService', |
46 | defaultMessage: '!!!Disable Service', | 54 | defaultMessage: '!!!Disable Service', |
@@ -49,16 +57,35 @@ const messages = defineMessages({ | |||
49 | id: 'tabs.item.enableService', | 57 | id: 'tabs.item.enableService', |
50 | defaultMessage: '!!!Enable Service', | 58 | defaultMessage: '!!!Enable Service', |
51 | }, | 59 | }, |
60 | hibernateService: { | ||
61 | id: 'tabs.item.hibernateService', | ||
62 | defaultMessage: '!!!Hibernate Service', | ||
63 | }, | ||
64 | wakeUpService: { | ||
65 | id: 'tabs.item.wakeUpService', | ||
66 | defaultMessage: '!!!Wake Up Service', | ||
67 | }, | ||
52 | deleteService: { | 68 | deleteService: { |
53 | id: 'tabs.item.deleteService', | 69 | id: 'tabs.item.deleteService', |
54 | defaultMessage: '!!!Delete Service', | 70 | defaultMessage: '!!!Delete Service', |
55 | }, | 71 | }, |
56 | confirmDeleteService: { | 72 | confirmDeleteService: { |
57 | id: 'tabs.item.confirmDeleteService', | 73 | id: 'tabs.item.confirmDeleteService', |
58 | defaultMessage: '!!!Do you really want to delete the {serviceName} service?', | 74 | defaultMessage: |
75 | '!!!Do you really want to delete the {serviceName} service?', | ||
59 | }, | 76 | }, |
60 | }); | 77 | }); |
61 | 78 | ||
79 | let pollIndicatorTransition = 'none'; | ||
80 | let polledTransition = 'none'; | ||
81 | let pollAnsweredTransition = 'none'; | ||
82 | |||
83 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
84 | pollIndicatorTransition = 'background 0.5s'; | ||
85 | polledTransition = 'background 0.1s'; | ||
86 | pollAnsweredTransition = 'background 0.1s'; | ||
87 | } | ||
88 | |||
62 | const styles = { | 89 | const styles = { |
63 | pollIndicator: { | 90 | pollIndicator: { |
64 | position: 'absolute', | 91 | position: 'absolute', |
@@ -67,7 +94,7 @@ const styles = { | |||
67 | height: 10, | 94 | height: 10, |
68 | borderRadius: 5, | 95 | borderRadius: 5, |
69 | background: 'gray', | 96 | background: 'gray', |
70 | transition: 'background 0.5s', | 97 | transition: pollIndicatorTransition, |
71 | }, | 98 | }, |
72 | pollIndicatorPoll: { | 99 | pollIndicatorPoll: { |
73 | left: 2, | 100 | left: 2, |
@@ -77,18 +104,20 @@ const styles = { | |||
77 | }, | 104 | }, |
78 | polled: { | 105 | polled: { |
79 | background: 'yellow !important', | 106 | background: 'yellow !important', |
80 | transition: 'background 0.1s', | 107 | transition: polledTransition, |
81 | }, | 108 | }, |
82 | pollAnswered: { | 109 | pollAnswered: { |
83 | background: 'green !important', | 110 | background: 'green !important', |
84 | transition: 'background 0.1s', | 111 | transition: pollAnsweredTransition, |
85 | }, | 112 | }, |
86 | stale: { | 113 | stale: { |
87 | background: 'red !important', | 114 | background: 'red !important', |
88 | }, | 115 | }, |
89 | }; | 116 | }; |
90 | 117 | ||
91 | @injectSheet(styles) @observer class TabItem extends Component { | 118 | @injectSheet(styles) |
119 | @observer | ||
120 | class TabItem extends Component { | ||
92 | static propTypes = { | 121 | static propTypes = { |
93 | classes: PropTypes.object.isRequired, | 122 | classes: PropTypes.object.isRequired, |
94 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 123 | service: PropTypes.instanceOf(ServiceModel).isRequired, |
@@ -97,10 +126,13 @@ const styles = { | |||
97 | reload: PropTypes.func.isRequired, | 126 | reload: PropTypes.func.isRequired, |
98 | toggleNotifications: PropTypes.func.isRequired, | 127 | toggleNotifications: PropTypes.func.isRequired, |
99 | toggleAudio: PropTypes.func.isRequired, | 128 | toggleAudio: PropTypes.func.isRequired, |
129 | toggleDarkMode: PropTypes.func.isRequired, | ||
100 | openSettings: PropTypes.func.isRequired, | 130 | openSettings: PropTypes.func.isRequired, |
101 | deleteService: PropTypes.func.isRequired, | 131 | deleteService: PropTypes.func.isRequired, |
102 | disableService: PropTypes.func.isRequired, | 132 | disableService: PropTypes.func.isRequired, |
103 | enableService: PropTypes.func.isRequired, | 133 | enableService: PropTypes.func.isRequired, |
134 | hibernateService: PropTypes.func.isRequired, | ||
135 | wakeUpService: PropTypes.func.isRequired, | ||
104 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | 136 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, |
105 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | 137 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, |
106 | }; | 138 | }; |
@@ -121,13 +153,17 @@ const styles = { | |||
121 | if (Date.now() - service.lastPoll < ms('0.2s')) { | 153 | if (Date.now() - service.lastPoll < ms('0.2s')) { |
122 | this.isPolled = true; | 154 | this.isPolled = true; |
123 | 155 | ||
124 | setTimeout(() => { this.isPolled = false; }, ms('1s')); | 156 | setTimeout(() => { |
157 | this.isPolled = false; | ||
158 | }, ms('1s')); | ||
125 | } | 159 | } |
126 | 160 | ||
127 | if (Date.now() - service.lastPollAnswer < ms('0.2s')) { | 161 | if (Date.now() - service.lastPollAnswer < ms('0.2s')) { |
128 | this.isPollAnswered = true; | 162 | this.isPollAnswered = true; |
129 | 163 | ||
130 | setTimeout(() => { this.isPollAnswered = false; }, ms('1s')); | 164 | setTimeout(() => { |
165 | this.isPollAnswered = false; | ||
166 | }, ms('1s')); | ||
131 | } | 167 | } |
132 | }); | 168 | }); |
133 | } | 169 | } |
@@ -142,67 +178,103 @@ const styles = { | |||
142 | reload, | 178 | reload, |
143 | toggleNotifications, | 179 | toggleNotifications, |
144 | toggleAudio, | 180 | toggleAudio, |
181 | toggleDarkMode, | ||
145 | deleteService, | 182 | deleteService, |
146 | disableService, | 183 | disableService, |
147 | enableService, | 184 | enableService, |
185 | hibernateService, | ||
186 | wakeUpService, | ||
148 | openSettings, | 187 | openSettings, |
149 | showMessageBadgeWhenMutedSetting, | 188 | showMessageBadgeWhenMutedSetting, |
150 | showMessageBadgesEvenWhenMuted, | 189 | showMessageBadgesEvenWhenMuted, |
151 | } = this.props; | 190 | } = this.props; |
152 | const { intl } = this.context; | 191 | const { intl } = this.context; |
153 | 192 | ||
154 | const menuTemplate = [{ | 193 | const menuTemplate = [ |
155 | label: service.name || service.recipe.name, | 194 | { |
156 | enabled: false, | 195 | label: service.name || service.recipe.name, |
157 | }, { | 196 | enabled: false, |
158 | type: 'separator', | 197 | }, |
159 | }, { | 198 | { |
160 | label: intl.formatMessage(messages.reload), | 199 | type: 'separator', |
161 | click: reload, | 200 | }, |
162 | accelerator: `${cmdKey}+R`, | 201 | { |
163 | }, { | 202 | label: intl.formatMessage(messages.reload), |
164 | label: intl.formatMessage(messages.edit), | 203 | click: reload, |
165 | click: () => openSettings({ | 204 | accelerator: `${shortcutKey()}+R`, |
166 | path: `services/edit/${service.id}`, | 205 | }, |
167 | }), | 206 | { |
168 | }, { | 207 | label: intl.formatMessage(messages.edit), |
169 | type: 'separator', | 208 | click: () => |
170 | }, { | 209 | openSettings({ |
171 | label: service.isNotificationEnabled | 210 | path: `services/edit/${service.id}`, |
172 | ? intl.formatMessage(messages.disableNotifications) | 211 | }), |
173 | : intl.formatMessage(messages.enableNotifications), | ||
174 | click: () => toggleNotifications(), | ||
175 | }, { | ||
176 | label: service.isMuted | ||
177 | ? intl.formatMessage(messages.enableAudio) | ||
178 | : intl.formatMessage(messages.disableAudio), | ||
179 | click: () => toggleAudio(), | ||
180 | }, { | ||
181 | label: intl.formatMessage(service.isEnabled ? messages.disableService : messages.enableService), | ||
182 | click: () => (service.isEnabled ? disableService() : enableService()), | ||
183 | }, { | ||
184 | type: 'separator', | ||
185 | }, { | ||
186 | label: intl.formatMessage(messages.deleteService), | ||
187 | click: () => { | ||
188 | const selection = dialog.showMessageBoxSync(app.mainWindow, { | ||
189 | type: 'question', | ||
190 | message: intl.formatMessage(messages.deleteService), | ||
191 | detail: intl.formatMessage(messages.confirmDeleteService, { serviceName: service.name || service.recipe.name }), | ||
192 | buttons: [ | ||
193 | 'Yes', | ||
194 | 'No', | ||
195 | ], | ||
196 | }); | ||
197 | if (selection === 0) { | ||
198 | deleteService(); | ||
199 | } | ||
200 | }, | 212 | }, |
201 | }]; | 213 | { |
214 | type: 'separator', | ||
215 | }, | ||
216 | { | ||
217 | label: service.isNotificationEnabled | ||
218 | ? intl.formatMessage(messages.disableNotifications) | ||
219 | : intl.formatMessage(messages.enableNotifications), | ||
220 | click: () => toggleNotifications(), | ||
221 | }, | ||
222 | { | ||
223 | label: service.isMuted | ||
224 | ? intl.formatMessage(messages.enableAudio) | ||
225 | : intl.formatMessage(messages.disableAudio), | ||
226 | click: () => toggleAudio(), | ||
227 | }, | ||
228 | { | ||
229 | label: service.isDarkModeEnabled | ||
230 | ? intl.formatMessage(messages.enableDarkMode) | ||
231 | : intl.formatMessage(messages.disableDarkMode), | ||
232 | click: () => toggleDarkMode(), | ||
233 | }, | ||
234 | { | ||
235 | label: intl.formatMessage( | ||
236 | service.isEnabled ? messages.disableService : messages.enableService, | ||
237 | ), | ||
238 | click: () => (service.isEnabled ? disableService() : enableService()), | ||
239 | }, | ||
240 | { | ||
241 | label: intl.formatMessage( | ||
242 | service.isHibernating | ||
243 | ? messages.wakeUpService | ||
244 | : messages.hibernateService, | ||
245 | ), | ||
246 | click: () => | ||
247 | (service.isHibernating ? wakeUpService() : hibernateService()), | ||
248 | enabled: service.canHibernate, | ||
249 | }, | ||
250 | { | ||
251 | type: 'separator', | ||
252 | }, | ||
253 | { | ||
254 | label: intl.formatMessage(messages.deleteService), | ||
255 | click: () => { | ||
256 | const selection = dialog.showMessageBoxSync(app.mainWindow, { | ||
257 | type: 'question', | ||
258 | message: intl.formatMessage(messages.deleteService), | ||
259 | detail: intl.formatMessage(messages.confirmDeleteService, { | ||
260 | serviceName: service.name || service.recipe.name, | ||
261 | }), | ||
262 | buttons: ['Yes', 'No'], | ||
263 | }); | ||
264 | if (selection === 0) { | ||
265 | deleteService(); | ||
266 | } | ||
267 | }, | ||
268 | }, | ||
269 | ]; | ||
202 | const menu = Menu.buildFromTemplate(menuTemplate); | 270 | const menu = Menu.buildFromTemplate(menuTemplate); |
203 | 271 | ||
204 | let notificationBadge = null; | 272 | let notificationBadge = null; |
205 | if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && service.isBadgeEnabled) { | 273 | if ( |
274 | (showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && | ||
275 | showMessageBadgesEvenWhenMuted && | ||
276 | service.isBadgeEnabled | ||
277 | ) { | ||
206 | notificationBadge = ( | 278 | notificationBadge = ( |
207 | <span> | 279 | <span> |
208 | {service.unreadDirectMessageCount > 0 && ( | 280 | {service.unreadDirectMessageCount > 0 && ( |
@@ -210,17 +282,13 @@ const styles = { | |||
210 | {service.unreadDirectMessageCount} | 282 | {service.unreadDirectMessageCount} |
211 | </span> | 283 | </span> |
212 | )} | 284 | )} |
213 | {service.unreadIndirectMessageCount > 0 | 285 | {service.unreadIndirectMessageCount > 0 && |
214 | && service.unreadDirectMessageCount === 0 | 286 | service.unreadDirectMessageCount === 0 && |
215 | && service.isIndirectMessageBadgeEnabled && ( | 287 | service.isIndirectMessageBadgeEnabled && ( |
216 | <span className="tab-item__message-count is-indirect"> | 288 | <span className="tab-item__message-count is-indirect">•</span> |
217 | • | ||
218 | </span> | ||
219 | )} | 289 | )} |
220 | {service.isHibernating && !service.isHibernationEnabled && ( | 290 | {service.isHibernating && ( |
221 | <span className="tab-item__message-count hibernating"> | 291 | <span className="tab-item__message-count hibernating">•</span> |
222 | • | ||
223 | </span> | ||
224 | )} | 292 | )} |
225 | </span> | 293 | </span> |
226 | ); | 294 | ); |
@@ -229,7 +297,8 @@ const styles = { | |||
229 | return ( | 297 | return ( |
230 | <li | 298 | <li |
231 | className={classnames({ | 299 | className={classnames({ |
232 | [classes.stale]: IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection, | 300 | [classes.stale]: |
301 | IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection, | ||
233 | 'tab-item': true, | 302 | 'tab-item': true, |
234 | 'is-active': service.isActive, | 303 | 'is-active': service.isActive, |
235 | 'has-custom-icon': service.hasCustomIcon, | 304 | 'has-custom-icon': service.hasCustomIcon, |
@@ -237,13 +306,11 @@ const styles = { | |||
237 | })} | 306 | })} |
238 | onClick={clickHandler} | 307 | onClick={clickHandler} |
239 | onContextMenu={() => menu.popup(getCurrentWindow())} | 308 | onContextMenu={() => menu.popup(getCurrentWindow())} |
240 | data-tip={`${service.name} ${shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''}`} | 309 | data-tip={`${service.name} ${ |
310 | shortcutIndex <= 9 ? `(${shortcutKey(false)}+${shortcutIndex})` : '' | ||
311 | }`} | ||
241 | > | 312 | > |
242 | <img | 313 | <img src={service.icon} className="tab-item__icon" alt="" /> |
243 | src={service.icon} | ||
244 | className="tab-item__icon" | ||
245 | alt="" | ||
246 | /> | ||
247 | {notificationBadge} | 314 | {notificationBadge} |
248 | {IS_SERVICE_DEBUGGING_ENABLED && ( | 315 | {IS_SERVICE_DEBUGGING_ENABLED && ( |
249 | <> | 316 | <> |
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js index 5e8260ad0..ab1e46c9f 100644 --- a/src/components/services/tabs/Tabbar.js +++ b/src/components/services/tabs/Tabbar.js | |||
@@ -15,8 +15,11 @@ export default @observer class TabBar extends Component { | |||
15 | reload: PropTypes.func.isRequired, | 15 | reload: PropTypes.func.isRequired, |
16 | toggleNotifications: PropTypes.func.isRequired, | 16 | toggleNotifications: PropTypes.func.isRequired, |
17 | toggleAudio: PropTypes.func.isRequired, | 17 | toggleAudio: PropTypes.func.isRequired, |
18 | toggleDarkMode: PropTypes.func.isRequired, | ||
18 | deleteService: PropTypes.func.isRequired, | 19 | deleteService: PropTypes.func.isRequired, |
19 | updateService: PropTypes.func.isRequired, | 20 | updateService: PropTypes.func.isRequired, |
21 | hibernateService: PropTypes.func.isRequired, | ||
22 | wakeUpService: PropTypes.func.isRequired, | ||
20 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, | 23 | showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, |
21 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, | 24 | showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, |
22 | }; | 25 | }; |
@@ -31,7 +34,7 @@ export default @observer class TabBar extends Component { | |||
31 | reorder({ oldIndex, newIndex }); | 34 | reorder({ oldIndex, newIndex }); |
32 | }; | 35 | }; |
33 | 36 | ||
34 | shouldPreventSorting = event => event.target.tagName !== 'LI'; | 37 | shouldPreventSorting = (event) => event.target.tagName !== 'LI'; |
35 | 38 | ||
36 | toggleService = ({ serviceId, isEnabled }) => { | 39 | toggleService = ({ serviceId, isEnabled }) => { |
37 | const { updateService } = this.props; | 40 | const { updateService } = this.props; |
@@ -55,6 +58,18 @@ export default @observer class TabBar extends Component { | |||
55 | this.toggleService({ serviceId, isEnabled: true }); | 58 | this.toggleService({ serviceId, isEnabled: true }); |
56 | } | 59 | } |
57 | 60 | ||
61 | hibernateService({ serviceId }) { | ||
62 | if (serviceId) { | ||
63 | this.props.hibernateService({ serviceId }); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | wakeUpService({ serviceId }) { | ||
68 | if (serviceId) { | ||
69 | this.props.wakeUpService({ serviceId }); | ||
70 | } | ||
71 | } | ||
72 | |||
58 | render() { | 73 | render() { |
59 | const { | 74 | const { |
60 | services, | 75 | services, |
@@ -64,6 +79,7 @@ export default @observer class TabBar extends Component { | |||
64 | reload, | 79 | reload, |
65 | toggleNotifications, | 80 | toggleNotifications, |
66 | toggleAudio, | 81 | toggleAudio, |
82 | toggleDarkMode, | ||
67 | deleteService, | 83 | deleteService, |
68 | showMessageBadgeWhenMutedSetting, | 84 | showMessageBadgeWhenMutedSetting, |
69 | showMessageBadgesEvenWhenMuted, | 85 | showMessageBadgesEvenWhenMuted, |
@@ -80,9 +96,12 @@ export default @observer class TabBar extends Component { | |||
80 | reload={reload} | 96 | reload={reload} |
81 | toggleNotifications={toggleNotifications} | 97 | toggleNotifications={toggleNotifications} |
82 | toggleAudio={toggleAudio} | 98 | toggleAudio={toggleAudio} |
99 | toggleDarkMode={toggleDarkMode} | ||
83 | deleteService={deleteService} | 100 | deleteService={deleteService} |
84 | disableService={args => this.disableService(args)} | 101 | disableService={(args) => this.disableService(args)} |
85 | enableService={args => this.enableService(args)} | 102 | enableService={(args) => this.enableService(args)} |
103 | hibernateService={(args) => this.hibernateService(args)} | ||
104 | wakeUpService={(args) => this.wakeUpService(args)} | ||
86 | openSettings={openSettings} | 105 | openSettings={openSettings} |
87 | distance={20} | 106 | distance={20} |
88 | axis="y" | 107 | axis="y" |
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'; | |||
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import ReactTooltip from 'react-tooltip'; | 5 | import ReactTooltip from 'react-tooltip'; |
6 | import { ProBadge, H1, H2 } from '@meetfranz/ui'; | 6 | import { H1, H2 } from '@meetfranz/ui'; |
7 | import moment from 'moment'; | ||
8 | 7 | ||
9 | import Loader from '../../ui/Loader'; | 8 | import Loader from '../../ui/Loader'; |
10 | import Button from '../../ui/Button'; | 9 | import Button from '../../ui/Button'; |
11 | import Infobox from '../../ui/Infobox'; | 10 | import Infobox from '../../ui/Infobox'; |
12 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; | ||
13 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
14 | import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config'; | 11 | import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config'; |
15 | 12 | ||
16 | const messages = defineMessages({ | 13 | const 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({ | |||
107 | class AccountDashboard extends Component { | 67 | class 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'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, intlShape } from 'react-intl'; |
4 | import { inject, observer } from 'mobx-react'; | 4 | import { inject, observer } from 'mobx-react'; |
5 | import { ProBadge } from '@meetfranz/ui'; | ||
6 | import { RouterStore } from 'mobx-react-router'; | 5 | import { RouterStore } from 'mobx-react-router'; |
7 | 6 | ||
8 | import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config'; | 7 | import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config'; |
@@ -11,7 +10,6 @@ import { workspaceStore } from '../../../features/workspaces'; | |||
11 | import UIStore from '../../../stores/UIStore'; | 10 | import UIStore from '../../../stores/UIStore'; |
12 | import SettingsStore from '../../../stores/SettingsStore'; | 11 | import SettingsStore from '../../../stores/SettingsStore'; |
13 | import UserStore from '../../../stores/UserStore'; | 12 | import UserStore from '../../../stores/UserStore'; |
14 | import { serviceLimitStore } from '../../../features/serviceLimit'; | ||
15 | 13 | ||
16 | const messages = defineMessages({ | 14 | const 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 | ||
7 | import { Button, Input } from '@meetfranz/forms'; | 7 | import { Button, Input } from '@meetfranz/forms'; |
8 | import injectSheet from 'react-jss'; | 8 | import injectSheet from 'react-jss'; |
9 | import { H3, H2, ProBadge } from '@meetfranz/ui'; | 9 | import { H3, H2 } from '@meetfranz/ui'; |
10 | import SearchInput from '../../ui/SearchInput'; | 10 | import SearchInput from '../../ui/SearchInput'; |
11 | import Infobox from '../../ui/Infobox'; | 11 | import Infobox from '../../ui/Infobox'; |
12 | import RecipeItem from './RecipeItem'; | 12 | import RecipeItem from './RecipeItem'; |
13 | import Loader from '../../ui/Loader'; | 13 | import Loader from '../../ui/Loader'; |
14 | import Appear from '../../ui/effects/Appear'; | 14 | import Appear from '../../ui/effects/Appear'; |
15 | import { FRANZ_SERVICE_REQUEST } from '../../../config'; | 15 | import { FRANZ_SERVICE_REQUEST } from '../../../config'; |
16 | import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; | ||
17 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | ||
18 | import RecipePreview from '../../../models/RecipePreview'; | 16 | import RecipePreview from '../../../models/RecipePreview'; |
19 | 17 | ||
20 | const messages = defineMessages({ | 18 | const 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 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
@@ -6,7 +6,6 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
6 | import normalizeUrl from 'normalize-url'; | 6 | import normalizeUrl from 'normalize-url'; |
7 | 7 | ||
8 | import Form from '../../../lib/Form'; | 8 | import Form from '../../../lib/Form'; |
9 | import User from '../../../models/User'; | ||
10 | import Recipe from '../../../models/Recipe'; | 9 | import Recipe from '../../../models/Recipe'; |
11 | import Service from '../../../models/Service'; | 10 | import Service from '../../../models/Service'; |
12 | import Tabs, { TabItem } from '../../ui/Tabs'; | 11 | import Tabs, { TabItem } from '../../ui/Tabs'; |
@@ -17,9 +16,6 @@ import Button from '../../ui/Button'; | |||
17 | import ImageUpload from '../../ui/ImageUpload'; | 16 | import ImageUpload from '../../ui/ImageUpload'; |
18 | import Select from '../../ui/Select'; | 17 | import Select from '../../ui/Select'; |
19 | 18 | ||
20 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | ||
21 | import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; | ||
22 | import { serviceLimitStore } from '../../../features/serviceLimit'; | ||
23 | import { isMac } from '../../../environment'; | 19 | import { isMac } from '../../../environment'; |
24 | import globalMessages from '../../../i18n/globalMessages'; | 20 | import 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'; | |||
10 | import FAB from '../../ui/FAB'; | 10 | import FAB from '../../ui/FAB'; |
11 | import ServiceItem from './ServiceItem'; | 11 | import ServiceItem from './ServiceItem'; |
12 | import Appear from '../../ui/effects/Appear'; | 12 | import Appear from '../../ui/effects/Appear'; |
13 | import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; | ||
14 | 13 | ||
15 | const messages = defineMessages({ | 14 | const 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 @@ | |||
1 | import { app, systemPreferences } from '@electron/remote'; | 1 | import { app, systemPreferences } from '@electron/remote'; |
2 | import React, { Component, Fragment } from 'react'; | 2 | import React, { Component } from 'react'; |
3 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
4 | import { observer } from 'mobx-react'; | 4 | import { observer } from 'mobx-react'; |
5 | import prettyBytes from 'pretty-bytes'; | 5 | import prettyBytes from 'pretty-bytes'; |
@@ -10,14 +10,13 @@ import Button from '../../ui/Button'; | |||
10 | import Toggle from '../../ui/Toggle'; | 10 | import Toggle from '../../ui/Toggle'; |
11 | import ToggleRaw from '../../ui/ToggleRaw'; | 11 | import ToggleRaw from '../../ui/ToggleRaw'; |
12 | import Select from '../../ui/Select'; | 12 | import Select from '../../ui/Select'; |
13 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | ||
14 | import Input from '../../ui/Input'; | 13 | import Input from '../../ui/Input'; |
15 | 14 | ||
16 | import { | 15 | import { |
17 | FRANZ_TRANSLATION, | 16 | FRANZ_TRANSLATION, |
18 | GITHUB_FRANZ_URL, | 17 | GITHUB_FRANZ_URL, |
19 | } from '../../../config'; | 18 | } from '../../../config'; |
20 | import { DEFAULT_APP_SETTINGS, isMac, isWindows } from '../../../environment'; | 19 | import { DEFAULT_APP_SETTINGS, isMac, isWindows, lockFerdiShortcutKey } from '../../../environment'; |
21 | import globalMessages from '../../../i18n/globalMessages'; | 20 | import globalMessages from '../../../i18n/globalMessages'; |
22 | 21 | ||
23 | const messages = defineMessages({ | 22 | const 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'; | |||
6 | import injectSheet from 'react-jss'; | 6 | import injectSheet from 'react-jss'; |
7 | import classnames from 'classnames'; | 7 | import classnames from 'classnames'; |
8 | 8 | ||
9 | import { Badge } from '@meetfranz/ui'; | ||
10 | import Loader from '../../ui/Loader'; | 9 | import Loader from '../../ui/Loader'; |
11 | import Button from '../../ui/Button'; | 10 | import Button from '../../ui/Button'; |
12 | import Infobox from '../../ui/Infobox'; | 11 | import Infobox from '../../ui/Infobox'; |
13 | import globalMessages from '../../../i18n/globalMessages'; | ||
14 | import UpgradeButton from '../../ui/UpgradeButton'; | ||
15 | import { LIVE_FRANZ_API } from '../../../config'; | 12 | import { LIVE_FRANZ_API } from '../../../config'; |
16 | 13 | ||
17 | const messages = defineMessages({ | 14 | const 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" |
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js deleted file mode 100644 index ec486e5d0..000000000 --- a/src/components/subscription/SubscriptionForm.js +++ /dev/null | |||
@@ -1,78 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import { H3, H2 } from '@meetfranz/ui'; | ||
8 | |||
9 | import { Button } from '@meetfranz/forms'; | ||
10 | import { FeatureList } from '../ui/FeatureList'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | submitButtonLabel: { | ||
14 | id: 'subscription.cta.choosePlan', | ||
15 | defaultMessage: '!!!Choose your plan', | ||
16 | }, | ||
17 | teaserHeadline: { | ||
18 | id: 'settings.account.headlineUpgradeAccount', | ||
19 | defaultMessage: '!!!Upgrade your account and get the full Franz experience', | ||
20 | }, | ||
21 | teaserText: { | ||
22 | id: 'subscription.teaser.intro', | ||
23 | defaultMessage: '!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!', | ||
24 | }, | ||
25 | includedFeatures: { | ||
26 | id: 'subscription.teaser.includedFeatures', | ||
27 | defaultMessage: '!!!Paid Franz Plans include:', | ||
28 | }, | ||
29 | }); | ||
30 | |||
31 | const styles = () => ({ | ||
32 | activateTrialButton: { | ||
33 | margin: [40, 'auto', 50], | ||
34 | display: 'flex', | ||
35 | }, | ||
36 | }); | ||
37 | |||
38 | export default @injectSheet(styles) @observer class SubscriptionForm extends Component { | ||
39 | static propTypes = { | ||
40 | selectPlan: PropTypes.func.isRequired, | ||
41 | isActivatingTrial: PropTypes.bool.isRequired, | ||
42 | classes: PropTypes.object.isRequired, | ||
43 | }; | ||
44 | |||
45 | static contextTypes = { | ||
46 | intl: intlShape, | ||
47 | }; | ||
48 | |||
49 | render() { | ||
50 | const { | ||
51 | isActivatingTrial, | ||
52 | selectPlan, | ||
53 | classes, | ||
54 | } = this.props; | ||
55 | const { intl } = this.context; | ||
56 | |||
57 | return ( | ||
58 | <> | ||
59 | <H2>{intl.formatMessage(messages.teaserHeadline)}</H2> | ||
60 | <p>{intl.formatMessage(messages.teaserText)}</p> | ||
61 | <Button | ||
62 | label={intl.formatMessage(messages.submitButtonLabel)} | ||
63 | className={classes.activateTrialButton} | ||
64 | busy={isActivatingTrial} | ||
65 | onClick={selectPlan} | ||
66 | /> | ||
67 | <div className="subscription__premium-info"> | ||
68 | <H3> | ||
69 | {intl.formatMessage(messages.includedFeatures)} | ||
70 | </H3> | ||
71 | <div className="subscription"> | ||
72 | <FeatureList /> | ||
73 | </div> | ||
74 | </div> | ||
75 | </> | ||
76 | ); | ||
77 | } | ||
78 | } | ||
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js deleted file mode 100644 index 0df43fd4b..000000000 --- a/src/components/subscription/SubscriptionPopup.js +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import Webview from 'react-electron-web-view'; | ||
6 | import ms from 'ms'; | ||
7 | |||
8 | import Button from '../ui/Button'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | buttonCancel: { | ||
12 | id: 'subscriptionPopup.buttonCancel', | ||
13 | defaultMessage: '!!!Cancel', | ||
14 | }, | ||
15 | buttonDone: { | ||
16 | id: 'subscriptionPopup.buttonDone', | ||
17 | defaultMessage: '!!!Done', | ||
18 | }, | ||
19 | }); | ||
20 | |||
21 | export default @observer class SubscriptionPopup extends Component { | ||
22 | static propTypes = { | ||
23 | url: PropTypes.string.isRequired, | ||
24 | closeWindow: PropTypes.func.isRequired, | ||
25 | completeCheck: PropTypes.func.isRequired, | ||
26 | isCompleted: PropTypes.bool.isRequired, | ||
27 | }; | ||
28 | |||
29 | static contextTypes = { | ||
30 | intl: intlShape, | ||
31 | }; | ||
32 | |||
33 | state = { | ||
34 | isFakeLoading: false, | ||
35 | }; | ||
36 | |||
37 | // We delay the window closing a bit in order to give | ||
38 | // the Recurly webhook a few seconds to do it's magic | ||
39 | delayedCloseWindow() { | ||
40 | this.setState({ | ||
41 | isFakeLoading: true, | ||
42 | }); | ||
43 | |||
44 | setTimeout(() => { | ||
45 | this.props.closeWindow(); | ||
46 | }, ms('1s')); | ||
47 | } | ||
48 | |||
49 | render() { | ||
50 | const { | ||
51 | url, closeWindow, completeCheck, isCompleted, | ||
52 | } = this.props; | ||
53 | const { intl } = this.context; | ||
54 | |||
55 | return ( | ||
56 | <div className="subscription-popup"> | ||
57 | <div className="subscription-popup__content"> | ||
58 | <Webview | ||
59 | className="subscription-popup__webview" | ||
60 | autosize | ||
61 | allowpopups | ||
62 | src={encodeURI(url)} | ||
63 | onDidNavigate={completeCheck} | ||
64 | onDidNavigateInPage={completeCheck} | ||
65 | /> | ||
66 | </div> | ||
67 | <div className="subscription-popup__toolbar franz-form"> | ||
68 | <Button | ||
69 | label={intl.formatMessage(messages.buttonCancel)} | ||
70 | buttonType="secondary" | ||
71 | onClick={closeWindow} | ||
72 | disabled={isCompleted} | ||
73 | /> | ||
74 | <Button | ||
75 | label={intl.formatMessage(messages.buttonDone)} | ||
76 | onClick={() => this.delayedCloseWindow()} | ||
77 | disabled={!isCompleted} | ||
78 | loaded={!this.state.isFakeLoading} | ||
79 | /> | ||
80 | </div> | ||
81 | </div> | ||
82 | ); | ||
83 | } | ||
84 | } | ||
diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js deleted file mode 100644 index d61b779ed..000000000 --- a/src/components/subscription/TrialForm.js +++ /dev/null | |||
@@ -1,115 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import { H3, H2 } from '@meetfranz/ui'; | ||
8 | |||
9 | import { Button } from '@meetfranz/forms'; | ||
10 | import { FeatureList } from '../ui/FeatureList'; | ||
11 | import { FeatureItem } from '../ui/FeatureItem'; | ||
12 | |||
13 | const messages = defineMessages({ | ||
14 | submitButtonLabel: { | ||
15 | id: 'subscription.cta.activateTrial', | ||
16 | defaultMessage: '!!!Yes, start the free Franz Professional trial', | ||
17 | }, | ||
18 | allOptionsButton: { | ||
19 | id: 'subscription.cta.allOptions', | ||
20 | defaultMessage: '!!!See all options', | ||
21 | }, | ||
22 | teaserHeadline: { | ||
23 | id: 'settings.account.headlineTrialUpgrade', | ||
24 | defaultMessage: '!!!Get the free 14 day Franz Professional Trial', | ||
25 | }, | ||
26 | includedFeatures: { | ||
27 | id: 'subscription.includedProFeatures', | ||
28 | defaultMessage: '!!!The Franz Professional Plan includes:', | ||
29 | }, | ||
30 | noStringsAttachedHeadline: { | ||
31 | id: 'pricing.trial.terms.headline', | ||
32 | defaultMessage: '!!!No strings attached', | ||
33 | }, | ||
34 | noCreditCard: { | ||
35 | id: 'pricing.trial.terms.noCreditCard', | ||
36 | defaultMessage: '!!!No credit card required', | ||
37 | }, | ||
38 | automaticTrialEnd: { | ||
39 | id: 'pricing.trial.terms.automaticTrialEnd', | ||
40 | defaultMessage: '!!!Your free trial ends automatically after 14 days', | ||
41 | }, | ||
42 | }); | ||
43 | |||
44 | const styles = theme => ({ | ||
45 | activateTrialButton: { | ||
46 | margin: [40, 'auto', 10], | ||
47 | display: 'flex', | ||
48 | }, | ||
49 | allOptionsButton: { | ||
50 | margin: [0, 0, 40], | ||
51 | background: 'none', | ||
52 | border: 'none', | ||
53 | color: theme.colorText, | ||
54 | }, | ||
55 | keyTerms: { | ||
56 | marginTop: 20, | ||
57 | }, | ||
58 | }); | ||
59 | |||
60 | export default @injectSheet(styles) @observer class TrialForm extends Component { | ||
61 | static propTypes = { | ||
62 | activateTrial: PropTypes.func.isRequired, | ||
63 | isActivatingTrial: PropTypes.bool.isRequired, | ||
64 | showAllOptions: PropTypes.func.isRequired, | ||
65 | classes: PropTypes.object.isRequired, | ||
66 | }; | ||
67 | |||
68 | static contextTypes = { | ||
69 | intl: intlShape, | ||
70 | }; | ||
71 | |||
72 | render() { | ||
73 | const { | ||
74 | isActivatingTrial, | ||
75 | activateTrial, | ||
76 | showAllOptions, | ||
77 | classes, | ||
78 | } = this.props; | ||
79 | const { intl } = this.context; | ||
80 | |||
81 | return ( | ||
82 | <> | ||
83 | <H2>{intl.formatMessage(messages.teaserHeadline)}</H2> | ||
84 | <H3 className={classes.keyTerms}> | ||
85 | {intl.formatMessage(messages.noStringsAttachedHeadline)} | ||
86 | </H3> | ||
87 | <ul> | ||
88 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> | ||
89 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> | ||
90 | </ul> | ||
91 | |||
92 | <Button | ||
93 | label={intl.formatMessage(messages.submitButtonLabel)} | ||
94 | className={classes.activateTrialButton} | ||
95 | busy={isActivatingTrial} | ||
96 | onClick={activateTrial} | ||
97 | /> | ||
98 | <Button | ||
99 | label={intl.formatMessage(messages.allOptionsButton)} | ||
100 | className={classes.allOptionsButton} | ||
101 | onClick={showAllOptions} | ||
102 | stretch | ||
103 | /> | ||
104 | <div className="subscription__premium-info"> | ||
105 | <H3> | ||
106 | {intl.formatMessage(messages.includedFeatures)} | ||
107 | </H3> | ||
108 | <div className="subscription"> | ||
109 | <FeatureList /> | ||
110 | </div> | ||
111 | </div> | ||
112 | </> | ||
113 | ); | ||
114 | } | ||
115 | } | ||
diff --git a/src/components/ui/ActivateTrialButton/index.js b/src/components/ui/ActivateTrialButton/index.js deleted file mode 100644 index 8f4d21f64..000000000 --- a/src/components/ui/ActivateTrialButton/index.js +++ /dev/null | |||
@@ -1,107 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import { Button } from '@meetfranz/forms'; | ||
8 | |||
9 | import UserStore from '../../../stores/UserStore'; | ||
10 | import UIStore from '../../../stores/UIStore'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | action: { | ||
14 | id: 'feature.delayApp.upgrade.action', | ||
15 | defaultMessage: '!!!Get a Franz Supporter License', | ||
16 | }, | ||
17 | actionTrial: { | ||
18 | id: 'feature.delayApp.trial.action', | ||
19 | defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional', | ||
20 | }, | ||
21 | shortAction: { | ||
22 | id: 'feature.delayApp.upgrade.actionShort', | ||
23 | defaultMessage: '!!!Upgrade account', | ||
24 | }, | ||
25 | shortActionTrial: { | ||
26 | id: 'feature.delayApp.trial.actionShort', | ||
27 | defaultMessage: '!!!Activate the free Franz Professional trial', | ||
28 | }, | ||
29 | noStringsAttachedHeadline: { | ||
30 | id: 'pricing.trial.terms.headline', | ||
31 | defaultMessage: '!!!No strings attached', | ||
32 | }, | ||
33 | noCreditCard: { | ||
34 | id: 'pricing.trial.terms.noCreditCard', | ||
35 | defaultMessage: '!!!No credit card required', | ||
36 | }, | ||
37 | automaticTrialEnd: { | ||
38 | id: 'pricing.trial.terms.automaticTrialEnd', | ||
39 | defaultMessage: '!!!Your free trial ends automatically after 14 days', | ||
40 | }, | ||
41 | }); | ||
42 | |||
43 | @inject('stores', 'actions') @observer | ||
44 | class ActivateTrialButton extends Component { | ||
45 | static propTypes = { | ||
46 | className: PropTypes.string, | ||
47 | short: PropTypes.bool, | ||
48 | gaEventInfo: PropTypes.shape({ | ||
49 | category: PropTypes.string.isRequired, | ||
50 | event: PropTypes.string.isRequired, | ||
51 | label: PropTypes.string, | ||
52 | }), | ||
53 | }; | ||
54 | |||
55 | static defaultProps = { | ||
56 | className: '', | ||
57 | short: false, | ||
58 | gaEventInfo: null, | ||
59 | } | ||
60 | |||
61 | static contextTypes = { | ||
62 | intl: intlShape, | ||
63 | }; | ||
64 | |||
65 | handleCTAClick() { | ||
66 | const { actions } = this.props; | ||
67 | |||
68 | actions.ui.openSettings({ path: 'user' }); | ||
69 | } | ||
70 | |||
71 | render() { | ||
72 | const { stores, className, short } = this.props; | ||
73 | const { intl } = this.context; | ||
74 | |||
75 | const { hadSubscription } = stores.user.data; | ||
76 | |||
77 | let label; | ||
78 | if (hadSubscription) { | ||
79 | label = short ? messages.shortAction : messages.action; | ||
80 | } else { | ||
81 | label = short ? messages.shortActionTrial : messages.actionTrial; | ||
82 | } | ||
83 | |||
84 | return ( | ||
85 | <Button | ||
86 | label={intl.formatMessage(label)} | ||
87 | className={classnames({ | ||
88 | [className]: className, | ||
89 | })} | ||
90 | buttonType="inverted" | ||
91 | onClick={this.handleCTAClick.bind(this)} | ||
92 | busy={stores.user.activateTrialRequest.isExecuting} | ||
93 | /> | ||
94 | ); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | export default ActivateTrialButton; | ||
99 | |||
100 | ActivateTrialButton.wrappedComponent.propTypes = { | ||
101 | stores: PropTypes.shape({ | ||
102 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
103 | }).isRequired, | ||
104 | actions: PropTypes.shape({ | ||
105 | ui: PropTypes.instanceOf(UIStore).isRequired, | ||
106 | }).isRequired, | ||
107 | }; | ||
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js index a7f6f4545..31d7ca249 100644 --- a/src/components/ui/AppLoader/index.js +++ b/src/components/ui/AppLoader/index.js | |||
@@ -37,7 +37,7 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component | |||
37 | 37 | ||
38 | componentDidMount() { | 38 | componentDidMount() { |
39 | this.interval = setInterval(() => { | 39 | this.interval = setInterval(() => { |
40 | this.setState(prevState => ({ | 40 | this.setState((prevState) => ({ |
41 | step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1, | 41 | step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1, |
42 | })); | 42 | })); |
43 | }, 2500); | 43 | }, 2500); |
diff --git a/src/components/ui/AppLoader/styles.js b/src/components/ui/AppLoader/styles.js index 755a56b40..9891e0387 100644 --- a/src/components/ui/AppLoader/styles.js +++ b/src/components/ui/AppLoader/styles.js | |||
@@ -1,3 +1,9 @@ | |||
1 | let sloganTransition = 'none'; | ||
2 | |||
3 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
4 | sloganTransition = 'opacity 1s ease'; | ||
5 | } | ||
6 | |||
1 | export default { | 7 | export default { |
2 | component: { | 8 | component: { |
3 | color: '#FFF', | 9 | color: '#FFF', |
@@ -5,7 +11,7 @@ export default { | |||
5 | slogan: { | 11 | slogan: { |
6 | display: 'block', | 12 | display: 'block', |
7 | opacity: 0, | 13 | opacity: 0, |
8 | transition: 'opacity 1s ease', | 14 | transition: sloganTransition, |
9 | position: 'absolute', | 15 | position: 'absolute', |
10 | textAlign: 'center', | 16 | textAlign: 'center', |
11 | width: '100%', | 17 | width: '100%', |
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js index 4926df470..646cf56ca 100644 --- a/src/components/ui/FeatureItem.js +++ b/src/components/ui/FeatureItem.js | |||
@@ -4,7 +4,7 @@ import { Icon } from '@meetfranz/ui'; | |||
4 | import classnames from 'classnames'; | 4 | import classnames from 'classnames'; |
5 | import { mdiCheckCircle } from '@mdi/js'; | 5 | import { mdiCheckCircle } from '@mdi/js'; |
6 | 6 | ||
7 | const styles = theme => ({ | 7 | const styles = (theme) => ({ |
8 | featureItem: { | 8 | featureItem: { |
9 | borderBottom: [1, 'solid', theme.defaultContentBorder], | 9 | borderBottom: [1, 'solid', theme.defaultContentBorder], |
10 | padding: [8, 0], | 10 | padding: [8, 0], |
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js index 72c799819..cf2664830 100644 --- a/src/components/ui/FeatureList.js +++ b/src/components/ui/FeatureList.js | |||
@@ -3,12 +3,11 @@ import PropTypes from 'prop-types'; | |||
3 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, intlShape } from 'react-intl'; |
4 | 4 | ||
5 | import { FeatureItem } from './FeatureItem'; | 5 | import { FeatureItem } from './FeatureItem'; |
6 | import { PLANS } from '../../config'; | ||
7 | 6 | ||
8 | const messages = defineMessages({ | 7 | const messages = defineMessages({ |
9 | availableRecipes: { | 8 | availableRecipes: { |
10 | id: 'pricing.features.recipes', | 9 | id: 'pricing.features.recipes', |
11 | defaultMessage: '!!!Choose from more than 70 Services', | 10 | defaultMessage: '!!!Choose from more than 70 Services', // TODO: Make this dynamic |
12 | }, | 11 | }, |
13 | accountSync: { | 12 | accountSync: { |
14 | id: 'pricing.features.accountSync', | 13 | id: 'pricing.features.accountSync', |
@@ -22,14 +21,6 @@ const messages = defineMessages({ | |||
22 | id: 'pricing.features.unlimitedServices', | 21 | id: 'pricing.features.unlimitedServices', |
23 | defaultMessage: '!!!Add unlimited services', | 22 | defaultMessage: '!!!Add unlimited services', |
24 | }, | 23 | }, |
25 | upToThreeServices: { | ||
26 | id: 'pricing.features.upToThreeServices', | ||
27 | defaultMessage: '!!!Add up to 3 services', | ||
28 | }, | ||
29 | upToSixServices: { | ||
30 | id: 'pricing.features.upToSixServices', | ||
31 | defaultMessage: '!!!Add up to 6 services', | ||
32 | }, | ||
33 | spellchecker: { | 24 | spellchecker: { |
34 | id: 'pricing.features.spellchecker', | 25 | id: 'pricing.features.spellchecker', |
35 | defaultMessage: '!!!Spellchecker support', | 26 | defaultMessage: '!!!Spellchecker support', |
@@ -58,31 +49,17 @@ const messages = defineMessages({ | |||
58 | id: 'pricing.features.teamManagement', | 49 | id: 'pricing.features.teamManagement', |
59 | defaultMessage: '!!!Team Management', | 50 | defaultMessage: '!!!Team Management', |
60 | }, | 51 | }, |
61 | appDelays: { | ||
62 | id: 'pricing.features.appDelays', | ||
63 | defaultMessage: '!!!No Waiting Screens', | ||
64 | }, | ||
65 | adFree: { | ||
66 | id: 'pricing.features.adFree', | ||
67 | defaultMessage: '!!!Forever ad-free', | ||
68 | }, | ||
69 | appDelayEnabled: { | ||
70 | id: 'pricing.features.appDelaysEnabled', | ||
71 | defaultMessage: '!!!Occasional Waiting Screens', | ||
72 | }, | ||
73 | }); | 52 | }); |
74 | 53 | ||
75 | export class FeatureList extends Component { | 54 | export class FeatureList extends Component { |
76 | static propTypes = { | 55 | static propTypes = { |
77 | className: PropTypes.string, | 56 | className: PropTypes.string, |
78 | featureClassName: PropTypes.string, | 57 | featureClassName: PropTypes.string, |
79 | plan: PropTypes.oneOf(Object.keys(PLANS)), | ||
80 | }; | 58 | }; |
81 | 59 | ||
82 | static defaultProps = { | 60 | static defaultProps = { |
83 | className: '', | 61 | className: '', |
84 | featureClassName: '', | 62 | featureClassName: '', |
85 | plan: false, | ||
86 | } | 63 | } |
87 | 64 | ||
88 | static contextTypes = { | 65 | static contextTypes = { |
@@ -93,53 +70,29 @@ export class FeatureList extends Component { | |||
93 | const { | 70 | const { |
94 | className, | 71 | className, |
95 | featureClassName, | 72 | featureClassName, |
96 | plan, | ||
97 | } = this.props; | 73 | } = this.props; |
98 | const { intl } = this.context; | 74 | const { intl } = this.context; |
99 | 75 | ||
100 | const features = []; | 76 | const features = [ |
101 | if (plan === PLANS.FREE) { | 77 | messages.availableRecipes, |
102 | features.push( | 78 | messages.accountSync, |
103 | messages.appDelayEnabled, | 79 | messages.desktopNotifications, |
104 | messages.upToThreeServices, | 80 | |
105 | messages.availableRecipes, | 81 | messages.spellchecker, |
106 | messages.accountSync, | 82 | |
107 | messages.desktopNotifications, | 83 | messages.workspaces, |
108 | ); | 84 | messages.customWebsites, |
109 | } else if (plan === PLANS.PERSONAL) { | 85 | messages.thirdPartyServices, |
110 | features.push( | 86 | |
111 | messages.upToSixServices, | 87 | messages.unlimitedServices, |
112 | messages.spellchecker, | 88 | messages.onPremise, |
113 | messages.appDelays, | 89 | messages.serviceProxies, |
114 | messages.adFree, | 90 | messages.teamManagement, |
115 | ); | 91 | ]; |
116 | } else if (plan === PLANS.PRO) { | ||
117 | features.push( | ||
118 | messages.unlimitedServices, | ||
119 | messages.workspaces, | ||
120 | messages.customWebsites, | ||
121 | // messages.onPremise, | ||
122 | messages.thirdPartyServices, | ||
123 | // messages.serviceProxies, | ||
124 | ); | ||
125 | } else { | ||
126 | features.push( | ||
127 | messages.unlimitedServices, | ||
128 | messages.spellchecker, | ||
129 | messages.workspaces, | ||
130 | messages.customWebsites, | ||
131 | messages.onPremise, | ||
132 | messages.thirdPartyServices, | ||
133 | messages.serviceProxies, | ||
134 | messages.teamManagement, | ||
135 | messages.appDelays, | ||
136 | messages.adFree, | ||
137 | ); | ||
138 | } | ||
139 | 92 | ||
140 | return ( | 93 | return ( |
141 | <ul className={className}> | 94 | <ul className={className}> |
142 | {features.map(feature => <FeatureItem name={intl.formatMessage(feature)} className={featureClassName} />)} | 95 | {features.map((feature) => <FeatureItem name={intl.formatMessage(feature)} className={featureClassName} />)} |
143 | </ul> | 96 | </ul> |
144 | ); | 97 | ); |
145 | } | 98 | } |
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js index 059610aec..bb4ea0565 100644 --- a/src/components/ui/ImageUpload.js +++ b/src/components/ui/ImageUpload.js | |||
@@ -26,16 +26,13 @@ export default @observer class ImageUpload extends Component { | |||
26 | 26 | ||
27 | dropzoneRef = null; | 27 | dropzoneRef = null; |
28 | 28 | ||
29 | imgPath = null; | ||
30 | |||
31 | onDrop(acceptedFiles) { | 29 | onDrop(acceptedFiles) { |
32 | const { field } = this.props; | 30 | const { field } = this.props; |
33 | 31 | ||
34 | acceptedFiles.forEach((file) => { | 32 | acceptedFiles.forEach((file) => { |
35 | this.imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path; | 33 | const imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path; |
36 | |||
37 | this.setState({ | 34 | this.setState({ |
38 | path: this.imgPath, | 35 | path: imgPath, |
39 | }); | 36 | }); |
40 | 37 | ||
41 | this.props.field.onDrop(file); | 38 | this.props.field.onDrop(file); |
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js index 7930d98b4..fd14b7018 100644 --- a/src/components/ui/Link.js +++ b/src/components/ui/Link.js | |||
@@ -48,7 +48,7 @@ export default @inject('stores') @observer class Link extends Component { | |||
48 | href={router.history.createHref(to)} | 48 | href={router.history.createHref(to)} |
49 | className={linkClasses} | 49 | className={linkClasses} |
50 | style={style} | 50 | style={style} |
51 | onClick={e => this.onClick(e)} | 51 | onClick={(e) => this.onClick(e)} |
52 | > | 52 | > |
53 | {children} | 53 | {children} |
54 | </a> | 54 | </a> |
diff --git a/src/components/ui/Modal/styles.js b/src/components/ui/Modal/styles.js index c2bebf9bb..f32c075ce 100644 --- a/src/components/ui/Modal/styles.js +++ b/src/components/ui/Modal/styles.js | |||
@@ -1,4 +1,4 @@ | |||
1 | export default theme => ({ | 1 | export default (theme) => ({ |
2 | component: { | 2 | component: { |
3 | zIndex: 500, | 3 | zIndex: 500, |
4 | position: 'absolute', | 4 | position: 'absolute', |
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js deleted file mode 100644 index 1e100f9d8..000000000 --- a/src/components/ui/PremiumFeatureContainer/index.js +++ /dev/null | |||
@@ -1,101 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { inject, observer } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import { oneOrManyChildElements } from '../../../prop-types'; | ||
8 | |||
9 | import UserStore from '../../../stores/UserStore'; | ||
10 | |||
11 | import styles from './styles'; | ||
12 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
13 | import UIStore from '../../../stores/UIStore'; | ||
14 | |||
15 | const messages = defineMessages({ | ||
16 | action: { | ||
17 | id: 'premiumFeature.button.upgradeAccount', | ||
18 | defaultMessage: '!!!Upgrade account', | ||
19 | }, | ||
20 | }); | ||
21 | |||
22 | @inject('stores', 'actions') @injectSheet(styles) @observer | ||
23 | class PremiumFeatureContainer extends Component { | ||
24 | static propTypes = { | ||
25 | classes: PropTypes.object.isRequired, | ||
26 | condition: PropTypes.oneOfType([ | ||
27 | PropTypes.bool, | ||
28 | PropTypes.func, | ||
29 | ]), | ||
30 | gaEventInfo: PropTypes.shape({ | ||
31 | category: PropTypes.string.isRequired, | ||
32 | event: PropTypes.string.isRequired, | ||
33 | label: PropTypes.string, | ||
34 | }), | ||
35 | }; | ||
36 | |||
37 | static defaultProps = { | ||
38 | condition: null, | ||
39 | gaEventInfo: null, | ||
40 | }; | ||
41 | |||
42 | static contextTypes = { | ||
43 | intl: intlShape, | ||
44 | }; | ||
45 | |||
46 | render() { | ||
47 | const { | ||
48 | classes, | ||
49 | children, | ||
50 | actions, | ||
51 | condition, | ||
52 | stores, | ||
53 | } = this.props; | ||
54 | |||
55 | const { intl } = this.context; | ||
56 | |||
57 | let showWrapper = !!condition; | ||
58 | |||
59 | if (condition === null) { | ||
60 | showWrapper = !stores.user.data.isPremium; | ||
61 | } else if (typeof condition === 'function') { | ||
62 | showWrapper = condition({ | ||
63 | isPremium: stores.user.data.isPremium, | ||
64 | features: stores.features.features, | ||
65 | }); | ||
66 | } | ||
67 | |||
68 | return showWrapper ? ( | ||
69 | <div className={classes.container}> | ||
70 | <div className={classes.titleContainer}> | ||
71 | <p className={classes.title}>Premium Feature</p> | ||
72 | <button | ||
73 | className={classes.actionButton} | ||
74 | type="button" | ||
75 | onClick={() => { | ||
76 | actions.ui.openSettings({ path: 'user' }); | ||
77 | }} | ||
78 | > | ||
79 | {intl.formatMessage(messages.action)} | ||
80 | </button> | ||
81 | </div> | ||
82 | <div className={classes.content}> | ||
83 | {children} | ||
84 | </div> | ||
85 | </div> | ||
86 | ) : children; | ||
87 | } | ||
88 | } | ||
89 | |||
90 | PremiumFeatureContainer.wrappedComponent.propTypes = { | ||
91 | children: oneOrManyChildElements.isRequired, | ||
92 | stores: PropTypes.shape({ | ||
93 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
94 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
95 | }).isRequired, | ||
96 | actions: PropTypes.shape({ | ||
97 | ui: PropTypes.instanceOf(UIStore).isRequired, | ||
98 | }).isRequired, | ||
99 | }; | ||
100 | |||
101 | export default PremiumFeatureContainer; | ||
diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js deleted file mode 100644 index 41881e044..000000000 --- a/src/components/ui/PremiumFeatureContainer/styles.js +++ /dev/null | |||
@@ -1,34 +0,0 @@ | |||
1 | export default theme => ({ | ||
2 | container: { | ||
3 | background: theme.colorSubscriptionContainerBackground, | ||
4 | border: theme.colorSubscriptionContainerBorder, | ||
5 | margin: [0, 0, 20, -20], | ||
6 | padding: 20, | ||
7 | 'border-radius': theme.borderRadius, | ||
8 | pointerEvents: 'none', | ||
9 | height: 'auto', | ||
10 | }, | ||
11 | titleContainer: { | ||
12 | display: 'flex', | ||
13 | }, | ||
14 | title: { | ||
15 | 'font-weight': 'bold', | ||
16 | color: theme.colorSubscriptionContainerTitle, | ||
17 | }, | ||
18 | actionButton: { | ||
19 | background: theme.colorSubscriptionContainerActionButtonBackground, | ||
20 | color: theme.colorSubscriptionContainerActionButtonColor, | ||
21 | 'margin-left': 'auto', | ||
22 | 'border-radius': theme.borderRadiusSmall, | ||
23 | padding: [4, 8], | ||
24 | 'font-size': 12, | ||
25 | pointerEvents: 'initial', | ||
26 | }, | ||
27 | content: { | ||
28 | opacity: 0.5, | ||
29 | 'margin-top': 20, | ||
30 | '& > :last-child': { | ||
31 | 'margin-bottom': 0, | ||
32 | }, | ||
33 | }, | ||
34 | }); | ||
diff --git a/src/components/ui/Radio.js b/src/components/ui/Radio.js index ba13aca63..e77714eb7 100644 --- a/src/components/ui/Radio.js +++ b/src/components/ui/Radio.js | |||
@@ -54,7 +54,7 @@ export default @observer class Radio extends Component { | |||
54 | </label> | 54 | </label> |
55 | )} | 55 | )} |
56 | <div className="franz-form__radio-wrapper"> | 56 | <div className="franz-form__radio-wrapper"> |
57 | {field.options.map(type => ( | 57 | {field.options.map((type) => ( |
58 | <label | 58 | <label |
59 | key={type.value} | 59 | key={type.value} |
60 | htmlFor={`${field.id}-${type.value}`} | 60 | htmlFor={`${field.id}-${type.value}`} |
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js index 78d6aae8b..0b25734dd 100644 --- a/src/components/ui/SearchInput.js +++ b/src/components/ui/SearchInput.js | |||
@@ -96,7 +96,7 @@ export default @observer class SearchInput extends Component { | |||
96 | type="text" | 96 | type="text" |
97 | placeholder={placeholder} | 97 | placeholder={placeholder} |
98 | value={value} | 98 | value={value} |
99 | onChange={e => this.onChange(e)} | 99 | onChange={(e) => this.onChange(e)} |
100 | ref={(ref) => { this.input = ref; }} | 100 | ref={(ref) => { this.input = ref; }} |
101 | /> | 101 | /> |
102 | </label> | 102 | </label> |
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js index b4511433c..e7a5eafa8 100644 --- a/src/components/ui/Select.js +++ b/src/components/ui/Select.js | |||
@@ -82,7 +82,7 @@ export default @observer class Select extends Component { | |||
82 | </label> | 82 | </label> |
83 | )} | 83 | )} |
84 | <select | 84 | <select |
85 | onChange={multiple ? e => this.multipleChange(e) : field.onChange} | 85 | onChange={multiple ? (e) => this.multipleChange(e) : field.onChange} |
86 | id={field.id} | 86 | id={field.id} |
87 | defaultValue={selected} | 87 | defaultValue={selected} |
88 | className="franz-form__select" | 88 | className="franz-form__select" |
@@ -90,7 +90,7 @@ export default @observer class Select extends Component { | |||
90 | multiple={multiple} | 90 | multiple={multiple} |
91 | ref={this.element} | 91 | ref={this.element} |
92 | > | 92 | > |
93 | {field.options.map(type => ( | 93 | {field.options.map((type) => ( |
94 | <option | 94 | <option |
95 | key={type.value} | 95 | key={type.value} |
96 | value={type.value} | 96 | value={type.value} |
diff --git a/src/components/ui/ServiceIcon.js b/src/components/ui/ServiceIcon.js index 0b9155a4e..b2dadeac3 100644 --- a/src/components/ui/ServiceIcon.js +++ b/src/components/ui/ServiceIcon.js | |||
@@ -6,7 +6,7 @@ import classnames from 'classnames'; | |||
6 | 6 | ||
7 | import ServiceModel from '../../models/Service'; | 7 | import ServiceModel from '../../models/Service'; |
8 | 8 | ||
9 | const styles = theme => ({ | 9 | const styles = (theme) => ({ |
10 | root: { | 10 | root: { |
11 | height: 'auto', | 11 | height: 'auto', |
12 | }, | 12 | }, |
diff --git a/src/components/ui/Slider.js b/src/components/ui/Slider.js index b00a6a3f8..f344449a0 100644 --- a/src/components/ui/Slider.js +++ b/src/components/ui/Slider.js | |||
@@ -53,7 +53,7 @@ export default @observer class Slider extends Component { | |||
53 | value={field.value} | 53 | value={field.value} |
54 | min="1" | 54 | min="1" |
55 | max="100" | 55 | max="100" |
56 | onChange={e => (!disabled ? this.onChange(e) : null)} | 56 | onChange={(e) => (!disabled ? this.onChange(e) : null)} |
57 | /> | 57 | /> |
58 | </div> | 58 | </div> |
59 | 59 | ||
diff --git a/src/components/ui/Tabs/TabItem.js b/src/components/ui/Tabs/TabItem.js index 546b05a4e..d0ef4e798 100644 --- a/src/components/ui/Tabs/TabItem.js +++ b/src/components/ui/Tabs/TabItem.js | |||
@@ -1,17 +1,15 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | 2 | ||
3 | import { oneOrManyChildElements } from '../../../prop-types'; | 3 | import { oneOrManyChildElements } from '../../../prop-types'; |
4 | 4 | ||
5 | export default class TabItem extends Component { | 5 | export default class TabItem extends Component { |
6 | static propTypes = { | 6 | static propTypes = { |
7 | children: oneOrManyChildElements.isRequired, | 7 | children: oneOrManyChildElements.isRequired, |
8 | } | 8 | }; |
9 | 9 | ||
10 | render() { | 10 | render() { |
11 | const { children } = this.props; | 11 | const { children } = this.props; |
12 | 12 | ||
13 | return ( | 13 | return <>{children}</>; |
14 | <>{children}</> | ||
15 | ); | ||
16 | } | 14 | } |
17 | } | 15 | } |
diff --git a/src/components/ui/Tabs/Tabs.js b/src/components/ui/Tabs/Tabs.js index e68dc3a1d..56c76f215 100644 --- a/src/components/ui/Tabs/Tabs.js +++ b/src/components/ui/Tabs/Tabs.js | |||
@@ -28,7 +28,7 @@ class Tab extends Component { | |||
28 | 28 | ||
29 | render() { | 29 | render() { |
30 | const { children: childElements } = this.props; | 30 | const { children: childElements } = this.props; |
31 | const children = childElements.filter(c => !!c); | 31 | const children = childElements.filter((c) => !!c); |
32 | 32 | ||
33 | if (children.length === 1) { | 33 | if (children.length === 1) { |
34 | return <div>{children}</div>; | 34 | return <div>{children}</div>; |
diff --git a/src/components/ui/Toggle.js b/src/components/ui/Toggle.js index 78fb77cbe..14330e5c7 100644 --- a/src/components/ui/Toggle.js +++ b/src/components/ui/Toggle.js | |||
@@ -59,7 +59,7 @@ export default @observer class Toggle extends Component { | |||
59 | name={field.name} | 59 | name={field.name} |
60 | value={field.name} | 60 | value={field.name} |
61 | checked={field.value} | 61 | checked={field.value} |
62 | onChange={e => (!disabled ? this.onChange(e) : null)} | 62 | onChange={(e) => (!disabled ? this.onChange(e) : null)} |
63 | /> | 63 | /> |
64 | </label> | 64 | </label> |
65 | {field.error && <div className={field.error}>{field.error}</div>} | 65 | {field.error && <div className={field.error}>{field.error}</div>} |
diff --git a/src/components/ui/ToggleRaw.js b/src/components/ui/ToggleRaw.js index dcaf664e1..4700127d4 100644 --- a/src/components/ui/ToggleRaw.js +++ b/src/components/ui/ToggleRaw.js | |||
@@ -64,7 +64,7 @@ export default @observer class ToggleRaw extends Component { | |||
64 | name={field.name} | 64 | name={field.name} |
65 | value={field.name} | 65 | value={field.name} |
66 | checked={field.value} | 66 | checked={field.value} |
67 | onChange={e => (!disabled ? this.onChange(e) : null)} | 67 | onChange={(e) => (!disabled ? this.onChange(e) : null)} |
68 | /> | 68 | /> |
69 | </label> | 69 | </label> |
70 | {field.error && <div className={field.error}>{field.error}</div>} | 70 | {field.error && <div className={field.error}>{field.error}</div>} |
diff --git a/src/components/ui/UpgradeButton/index.js b/src/components/ui/UpgradeButton/index.js deleted file mode 100644 index eade46cfd..000000000 --- a/src/components/ui/UpgradeButton/index.js +++ /dev/null | |||
@@ -1,83 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import { Button } from '@meetfranz/forms'; | ||
7 | |||
8 | import UserStore from '../../../stores/UserStore'; | ||
9 | import ActivateTrialButton from '../ActivateTrialButton'; | ||
10 | import UIStore from '../../../stores/UIStore'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | upgradeToPro: { | ||
14 | id: 'global.upgradeButton.upgradeToPro', | ||
15 | defaultMessage: '!!!Upgrade to Franz Professional', | ||
16 | }, | ||
17 | }); | ||
18 | |||
19 | @inject('stores', 'actions') @observer | ||
20 | class UpgradeButton extends Component { | ||
21 | static propTypes = { | ||
22 | // eslint-disable-next-line | ||
23 | classes: PropTypes.object.isRequired, | ||
24 | className: PropTypes.string, | ||
25 | gaEventInfo: PropTypes.shape({ | ||
26 | category: PropTypes.string.isRequired, | ||
27 | event: PropTypes.string.isRequired, | ||
28 | label: PropTypes.string, | ||
29 | }), | ||
30 | requiresPro: PropTypes.bool, | ||
31 | }; | ||
32 | |||
33 | static defaultProps = { | ||
34 | className: '', | ||
35 | gaEventInfo: null, | ||
36 | requiresPro: false, | ||
37 | } | ||
38 | |||
39 | static contextTypes = { | ||
40 | intl: intlShape, | ||
41 | }; | ||
42 | |||
43 | handleCTAClick() { | ||
44 | const { actions } = this.props; | ||
45 | |||
46 | actions.ui.openSettings({ path: 'user' }); | ||
47 | } | ||
48 | |||
49 | render() { | ||
50 | const { stores, requiresPro } = this.props; | ||
51 | const { intl } = this.context; | ||
52 | |||
53 | const { isPremium, isPersonal } = stores.user; | ||
54 | |||
55 | if (isPremium && isPersonal && requiresPro) { | ||
56 | return ( | ||
57 | <Button | ||
58 | label={intl.formatMessage(messages.upgradeToPro)} | ||
59 | onClick={this.handleCTAClick.bind(this)} | ||
60 | className={this.props.className} | ||
61 | buttonType="inverted" | ||
62 | /> | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | if (!isPremium) { | ||
67 | return <ActivateTrialButton {...this.props} />; | ||
68 | } | ||
69 | |||
70 | return null; | ||
71 | } | ||
72 | } | ||
73 | |||
74 | export default UpgradeButton; | ||
75 | |||
76 | UpgradeButton.wrappedComponent.propTypes = { | ||
77 | stores: PropTypes.shape({ | ||
78 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
79 | }).isRequired, | ||
80 | actions: PropTypes.shape({ | ||
81 | ui: PropTypes.instanceOf(UIStore).isRequired, | ||
82 | }).isRequired, | ||
83 | }; | ||
diff --git a/src/components/ui/WebviewLoader/styles.js b/src/components/ui/WebviewLoader/styles.js index dbd75db8a..5d58011fe 100644 --- a/src/components/ui/WebviewLoader/styles.js +++ b/src/components/ui/WebviewLoader/styles.js | |||
@@ -1,4 +1,4 @@ | |||
1 | export default theme => ({ | 1 | export default (theme) => ({ |
2 | component: { | 2 | component: { |
3 | background: theme.colorWebviewLoaderBackground, | 3 | background: theme.colorWebviewLoaderBackground, |
4 | padding: 20, | 4 | padding: 20, |
diff --git a/src/components/util/ErrorBoundary/styles.js b/src/components/util/ErrorBoundary/styles.js index 0960546ff..51b36fdf3 100644 --- a/src/components/util/ErrorBoundary/styles.js +++ b/src/components/util/ErrorBoundary/styles.js | |||
@@ -1,4 +1,4 @@ | |||
1 | export default theme => ({ | 1 | export default (theme) => ({ |
2 | component: { | 2 | component: { |
3 | display: 'flex', | 3 | display: 'flex', |
4 | width: '100%', | 4 | width: '100%', |