diff options
author | vantezzen <hello@vantezzen.io> | 2019-10-24 15:55:22 +0200 |
---|---|---|
committer | vantezzen <hello@vantezzen.io> | 2019-10-24 15:55:22 +0200 |
commit | bacb5b940333f7e3af9f9d978d1d72c75f1aa321 (patch) | |
tree | 50c8ecb3d08e997106e48d1de5b904a2dba30991 /src | |
parent | Merge translations (diff) | |
parent | Switch to beta version (diff) | |
download | ferdium-app-bacb5b940333f7e3af9f9d978d1d72c75f1aa321.tar.gz ferdium-app-bacb5b940333f7e3af9f9d978d1d72c75f1aa321.tar.zst ferdium-app-bacb5b940333f7e3af9f9d978d1d72c75f1aa321.zip |
Merge branch 'develop' into l10n_develop
Diffstat (limited to 'src')
74 files changed, 2808 insertions, 738 deletions
diff --git a/src/actions/index.js b/src/actions/index.js index 336344d76..9d3684edc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js | |||
@@ -14,6 +14,8 @@ import requests from './requests'; | |||
14 | import announcements from '../features/announcements/actions'; | 14 | import announcements from '../features/announcements/actions'; |
15 | import workspaces from '../features/workspaces/actions'; | 15 | import workspaces from '../features/workspaces/actions'; |
16 | import todos from '../features/todos/actions'; | 16 | import todos from '../features/todos/actions'; |
17 | import planSelection from '../features/planSelection/actions'; | ||
18 | import trialStatusBar from '../features/trialStatusBar/actions'; | ||
17 | 19 | ||
18 | const actions = Object.assign({}, { | 20 | const actions = Object.assign({}, { |
19 | service, | 21 | service, |
@@ -33,4 +35,6 @@ export default Object.assign( | |||
33 | { announcements }, | 35 | { announcements }, |
34 | { workspaces }, | 36 | { workspaces }, |
35 | { todos }, | 37 | { todos }, |
38 | { planSelection }, | ||
39 | { trialStatusBar }, | ||
36 | ); | 40 | ); |
diff --git a/src/actions/payment.js b/src/actions/payment.js index 2aaefc025..f61faf197 100644 --- a/src/actions/payment.js +++ b/src/actions/payment.js | |||
@@ -4,5 +4,9 @@ export default { | |||
4 | createHostedPage: { | 4 | createHostedPage: { |
5 | planId: PropTypes.string.isRequired, | 5 | planId: PropTypes.string.isRequired, |
6 | }, | 6 | }, |
7 | upgradeAccount: { | ||
8 | planId: PropTypes.string.isRequired, | ||
9 | onCloseWindow: PropTypes.func, | ||
10 | }, | ||
7 | createDashboardUrl: {}, | 11 | createDashboardUrl: {}, |
8 | }; | 12 | }; |
diff --git a/src/actions/user.js b/src/actions/user.js index 5d7d9a899..7061a367a 100644 --- a/src/actions/user.js +++ b/src/actions/user.js | |||
@@ -11,8 +11,10 @@ export default { | |||
11 | lastname: PropTypes.string.isRequired, | 11 | lastname: PropTypes.string.isRequired, |
12 | email: PropTypes.string.isRequired, | 12 | email: PropTypes.string.isRequired, |
13 | password: PropTypes.string.isRequired, | 13 | password: PropTypes.string.isRequired, |
14 | accountType: PropTypes.string.isRequired, | 14 | accountType: PropTypes.string, |
15 | company: PropTypes.string, | 15 | company: PropTypes.string, |
16 | plan: PropTypes.string, | ||
17 | currency: PropTypes.string, | ||
16 | }, | 18 | }, |
17 | retrievePassword: { | 19 | retrievePassword: { |
18 | email: PropTypes.string.isRequired, | 20 | email: PropTypes.string.isRequired, |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index 02f6b389d..a5d636b4e 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -87,7 +87,7 @@ export default class ServerApi { | |||
87 | } | 87 | } |
88 | const trial = await request.json(); | 88 | const trial = await request.json(); |
89 | 89 | ||
90 | debug('ServerApi::signup resolves', trial); | 90 | debug('ServerApi::activateTrial resolves', trial); |
91 | return true; | 91 | return true; |
92 | } | 92 | } |
93 | 93 | ||
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index e58016e25..e25121de0 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js | |||
@@ -70,7 +70,7 @@ const messages = defineMessages({ | |||
70 | }, | 70 | }, |
71 | }); | 71 | }); |
72 | 72 | ||
73 | export default @observer @inject('actions') class Login extends Component { | 73 | export default @inject('actions') @observer class Login extends Component { |
74 | static propTypes = { | 74 | static propTypes = { |
75 | onSubmit: PropTypes.func.isRequired, | 75 | onSubmit: PropTypes.func.isRequired, |
76 | isSubmitting: PropTypes.bool.isRequired, | 76 | isSubmitting: PropTypes.bool.isRequired, |
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index aadb18d91..593cb9c4b 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js | |||
@@ -13,12 +13,20 @@ import { FeatureList } from '../ui/FeatureList'; | |||
13 | 13 | ||
14 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
15 | headline: { | 15 | headline: { |
16 | id: 'pricing.trial.headline', | 16 | id: 'pricing.trial.headline.pro', |
17 | defaultMessage: '!!!Franz Professional', | 17 | defaultMessage: '!!!Hi {name}, welcome to Franz', |
18 | }, | 18 | }, |
19 | personalOffer: { | 19 | specialTreat: { |
20 | id: 'pricing.trial.subheadline', | 20 | id: 'pricing.trial.intro.specialTreat', |
21 | defaultMessage: '!!!Your personal welcome offer:', | 21 | defaultMessage: '!!!We have a special treat for you.', |
22 | }, | ||
23 | tryPro: { | ||
24 | id: 'pricing.trial.intro.tryPro', | ||
25 | defaultMessage: '!!!Enjoy the full Franz Professional experience completely free for 14 days.', | ||
26 | }, | ||
27 | happyMessaging: { | ||
28 | id: 'pricing.trial.intro.happyMessaging', | ||
29 | defaultMessage: '!!!Happy messaging,', | ||
22 | }, | 30 | }, |
23 | noStringsAttachedHeadline: { | 31 | noStringsAttachedHeadline: { |
24 | id: 'pricing.trial.terms.headline', | 32 | id: 'pricing.trial.terms.headline', |
@@ -32,13 +40,21 @@ const messages = defineMessages({ | |||
32 | id: 'pricing.trial.terms.automaticTrialEnd', | 40 | id: 'pricing.trial.terms.automaticTrialEnd', |
33 | defaultMessage: '!!!Your free trial ends automatically after 14 days', | 41 | defaultMessage: '!!!Your free trial ends automatically after 14 days', |
34 | }, | 42 | }, |
43 | trialWorth: { | ||
44 | id: 'pricing.trial.terms.trialWorth', | ||
45 | defaultMessage: '!!!Free trial (normally {currency}{price} per month)', | ||
46 | }, | ||
35 | activationError: { | 47 | activationError: { |
36 | id: 'pricing.trial.error', | 48 | id: 'pricing.trial.error', |
37 | defaultMessage: '!!!Sorry, we could not activate your trial!', | 49 | defaultMessage: '!!!Sorry, we could not activate your trial!', |
38 | }, | 50 | }, |
39 | ctaAccept: { | 51 | ctaAccept: { |
40 | id: 'pricing.trial.cta.accept', | 52 | id: 'pricing.trial.cta.accept', |
41 | defaultMessage: '!!!Yes, upgrade my account to Franz Professional', | 53 | defaultMessage: '!!!Start my 14-day Franz Professional Trial ', |
54 | }, | ||
55 | ctaStart: { | ||
56 | id: 'pricing.trial.cta.start', | ||
57 | defaultMessage: '!!!Start using Franz', | ||
42 | }, | 58 | }, |
43 | ctaSkip: { | 59 | ctaSkip: { |
44 | id: 'pricing.trial.cta.skip', | 60 | id: 'pricing.trial.cta.skip', |
@@ -58,6 +74,7 @@ const styles = theme => ({ | |||
58 | welcomeOffer: { | 74 | welcomeOffer: { |
59 | textAlign: 'center', | 75 | textAlign: 'center', |
60 | fontWeight: 'bold', | 76 | fontWeight: 'bold', |
77 | marginBottom: '6 !important', | ||
61 | }, | 78 | }, |
62 | keyTerms: { | 79 | keyTerms: { |
63 | textAlign: 'center', | 80 | textAlign: 'center', |
@@ -93,6 +110,34 @@ const styles = theme => ({ | |||
93 | margin: [20, 0, 0], | 110 | margin: [20, 0, 0], |
94 | color: theme.styleTypes.danger.accent, | 111 | color: theme.styleTypes.danger.accent, |
95 | }, | 112 | }, |
113 | priceContainer: { | ||
114 | display: 'flex', | ||
115 | justifyContent: 'space-evenly', | ||
116 | margin: [10, 0, 15], | ||
117 | }, | ||
118 | price: { | ||
119 | '& sup': { | ||
120 | verticalAlign: 14, | ||
121 | fontSize: 20, | ||
122 | }, | ||
123 | }, | ||
124 | figure: { | ||
125 | fontSize: 40, | ||
126 | }, | ||
127 | regularPrice: { | ||
128 | position: 'relative', | ||
129 | |||
130 | '&:before': { | ||
131 | content: '" "', | ||
132 | position: 'absolute', | ||
133 | width: '130%', | ||
134 | height: 1, | ||
135 | top: 14, | ||
136 | left: -12, | ||
137 | borderBottom: [3, 'solid', 'red'], | ||
138 | transform: 'rotateZ(-20deg)', | ||
139 | }, | ||
140 | }, | ||
96 | }); | 141 | }); |
97 | 142 | ||
98 | export default @injectSheet(styles) @observer class Signup extends Component { | 143 | export default @injectSheet(styles) @observer class Signup extends Component { |
@@ -101,7 +146,11 @@ export default @injectSheet(styles) @observer class Signup extends Component { | |||
101 | isLoadingRequiredData: PropTypes.bool.isRequired, | 146 | isLoadingRequiredData: PropTypes.bool.isRequired, |
102 | isActivatingTrial: PropTypes.bool.isRequired, | 147 | isActivatingTrial: PropTypes.bool.isRequired, |
103 | trialActivationError: PropTypes.bool.isRequired, | 148 | trialActivationError: PropTypes.bool.isRequired, |
149 | canSkipTrial: PropTypes.bool.isRequired, | ||
104 | classes: PropTypes.object.isRequired, | 150 | classes: PropTypes.object.isRequired, |
151 | currency: PropTypes.string.isRequired, | ||
152 | price: PropTypes.number.isRequired, | ||
153 | name: PropTypes.string.isRequired, | ||
105 | }; | 154 | }; |
106 | 155 | ||
107 | static contextTypes = { | 156 | static contextTypes = { |
@@ -114,10 +163,16 @@ export default @injectSheet(styles) @observer class Signup extends Component { | |||
114 | isLoadingRequiredData, | 163 | isLoadingRequiredData, |
115 | isActivatingTrial, | 164 | isActivatingTrial, |
116 | trialActivationError, | 165 | trialActivationError, |
166 | canSkipTrial, | ||
117 | classes, | 167 | classes, |
168 | currency, | ||
169 | price, | ||
170 | name, | ||
118 | } = this.props; | 171 | } = this.props; |
119 | const { intl } = this.context; | 172 | const { intl } = this.context; |
120 | 173 | ||
174 | const [intPart, fractionPart] = (price).toString().split('.'); | ||
175 | |||
121 | return ( | 176 | return ( |
122 | <div className={classnames('auth__scroll-container', classes.container)}> | 177 | <div className={classnames('auth__scroll-container', classes.container)}> |
123 | <div className={classnames('auth__container', 'auth__container--signup', classes.content)}> | 178 | <div className={classnames('auth__container', 'auth__container--signup', classes.content)}> |
@@ -129,30 +184,51 @@ export default @injectSheet(styles) @observer class Signup extends Component { | |||
129 | alt="" | 184 | alt="" |
130 | /> | 185 | /> |
131 | )} | 186 | )} |
132 | <p className={classes.welcomeOffer}>{intl.formatMessage(messages.personalOffer)}</p> | 187 | <h1>{intl.formatMessage(messages.headline, { name })}</h1> |
133 | <h1>{intl.formatMessage(messages.headline)}</h1> | ||
134 | <div className="auth__letter"> | 188 | <div className="auth__letter"> |
135 | <p> | 189 | <p> |
136 | We built Franz with a lot of effort, manpower and love, | 190 | {intl.formatMessage(messages.specialTreat)} |
137 | to boost up your messaging experience. | ||
138 | <br /> | 191 | <br /> |
139 | </p> | 192 | </p> |
140 | <p> | 193 | <p> |
141 | Get the free 14 day Franz Professional trial and see your communication evolving. | 194 | {intl.formatMessage(messages.tryPro)} |
142 | <br /> | 195 | <br /> |
143 | </p> | 196 | </p> |
144 | <p> | 197 | <p> |
145 | Thanks for being a hero. | 198 | {intl.formatMessage(messages.happyMessaging)} |
146 | </p> | 199 | </p> |
147 | <p> | 200 | <p> |
148 | <strong>Stefan Malzner</strong> | 201 | <strong>Stefan Malzner</strong> |
149 | </p> | 202 | </p> |
150 | </div> | 203 | </div> |
204 | <div className={classes.priceContainer}> | ||
205 | <p className={classnames(classes.price, classes.regularPrice)}> | ||
206 | <span className={classes.figure}> | ||
207 | {currency} | ||
208 | {intPart} | ||
209 | </span> | ||
210 | <sup>{fractionPart}</sup> | ||
211 | </p> | ||
212 | <p className={classnames(classes.price, classes.trialPrice)}> | ||
213 | <span className={classes.figure}> | ||
214 | {currency} | ||
215 | 0 | ||
216 | </span> | ||
217 | <sup>00</sup> | ||
218 | </p> | ||
219 | </div> | ||
151 | <div className={classes.keyTerms}> | 220 | <div className={classes.keyTerms}> |
152 | <H2> | 221 | <H2> |
153 | {intl.formatMessage(messages.noStringsAttachedHeadline)} | 222 | {intl.formatMessage(messages.noStringsAttachedHeadline)} |
154 | </H2> | 223 | </H2> |
155 | <ul className={classes.keyTermsList}> | 224 | <ul className={classes.keyTermsList}> |
225 | <FeatureItem | ||
226 | icon="👉" | ||
227 | name={intl.formatMessage(messages.trialWorth, { | ||
228 | currency, | ||
229 | price, | ||
230 | })} | ||
231 | /> | ||
156 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> | 232 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> |
157 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> | 233 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> |
158 | </ul> | 234 | </ul> |
@@ -161,33 +237,23 @@ export default @injectSheet(styles) @observer class Signup extends Component { | |||
161 | <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p> | 237 | <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p> |
162 | )} | 238 | )} |
163 | <Button | 239 | <Button |
164 | label={intl.formatMessage(messages.ctaAccept)} | 240 | label={intl.formatMessage(!canSkipTrial ? messages.ctaStart : messages.ctaAccept)} |
165 | className={classes.cta} | 241 | className={classes.cta} |
166 | onClick={onSubmit} | 242 | onClick={onSubmit} |
167 | busy={isActivatingTrial} | 243 | busy={isActivatingTrial} |
168 | disabled={isLoadingRequiredData || isActivatingTrial} | 244 | disabled={isLoadingRequiredData || isActivatingTrial} |
169 | /> | 245 | /> |
170 | <p className={classes.skipLink}> | 246 | {canSkipTrial && ( |
171 | <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a> | 247 | <p className={classes.skipLink}> |
172 | </p> | 248 | <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a> |
249 | </p> | ||
250 | )} | ||
173 | </form> | 251 | </form> |
174 | </div> | 252 | </div> |
175 | <div className={classes.featureContainer}> | 253 | <div className={classes.featureContainer}> |
176 | <H2> | 254 | <H2> |
177 | {intl.formatMessage(messages.featuresHeadline)} | 255 | {intl.formatMessage(messages.featuresHeadline)} |
178 | </H2> | 256 | </H2> |
179 | {/* <ul className={classes.features}> | ||
180 | <FeatureItem name="Add unlimited services" className={classes.featureItem} /> | ||
181 | <FeatureItem name="Spellchecker support" className={classes.featureItem} /> | ||
182 | <FeatureItem name="Workspaces" className={classes.featureItem} /> | ||
183 | <FeatureItem name="Add Custom Websites" className={classes.featureItem} /> | ||
184 | <FeatureItem name="On-premise & other Hosted Services" className={classes.featureItem} /> | ||
185 | <FeatureItem name="Install 3rd party services" className={classes.featureItem} /> | ||
186 | <FeatureItem name="Service Proxies" className={classes.featureItem} /> | ||
187 | <FeatureItem name="Team Management" className={classes.featureItem} /> | ||
188 | <FeatureItem name="No Waiting Screens" className={classes.featureItem} /> | ||
189 | <FeatureItem name="Forever ad-free" className={classes.featureItem} /> | ||
190 | </ul> */} | ||
191 | <FeatureList /> | 257 | <FeatureList /> |
192 | </div> | 258 | </div> |
193 | </div> | 259 | </div> |
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js index 47e9daf18..a166155a7 100644 --- a/src/components/auth/Signup.js +++ b/src/components/auth/Signup.js | |||
@@ -74,7 +74,7 @@ const messages = defineMessages({ | |||
74 | }, | 74 | }, |
75 | }); | 75 | }); |
76 | 76 | ||
77 | export default @observer @inject('actions') class Signup extends Component { | 77 | export default @inject('actions') @observer class Signup extends Component { |
78 | static propTypes = { | 78 | static propTypes = { |
79 | onSubmit: PropTypes.func.isRequired, | 79 | onSubmit: PropTypes.func.isRequired, |
80 | isSubmitting: PropTypes.bool.isRequired, | 80 | isSubmitting: PropTypes.bool.isRequired, |
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js index 2ca8b430f..1453c1d7c 100644 --- a/src/components/auth/Welcome.js +++ b/src/components/auth/Welcome.js | |||
@@ -22,7 +22,7 @@ const messages = defineMessages({ | |||
22 | }, | 22 | }, |
23 | }); | 23 | }); |
24 | 24 | ||
25 | export default @observer @inject('actions') class Login extends Component { | 25 | export default @inject('actions') @observer class Login extends Component { |
26 | static propTypes = { | 26 | static propTypes = { |
27 | loginRoute: PropTypes.string.isRequired, | 27 | loginRoute: PropTypes.string.isRequired, |
28 | signupRoute: PropTypes.string.isRequired, | 28 | signupRoute: PropTypes.string.isRequired, |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 2b0719f92..80e6daf19 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -19,6 +19,8 @@ import { workspaceStore } from '../../features/workspaces'; | |||
19 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; | 19 | import AppUpdateInfoBar from '../AppUpdateInfoBar'; |
20 | import TrialActivationInfoBar from '../TrialActivationInfoBar'; | 20 | import TrialActivationInfoBar from '../TrialActivationInfoBar'; |
21 | import Todos from '../../features/todos/containers/TodosScreen'; | 21 | import Todos from '../../features/todos/containers/TodosScreen'; |
22 | import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen'; | ||
23 | import TrialStatusBar from '../../features/trialStatusBar/containers/TrialStatusBarScreen'; | ||
22 | 24 | ||
23 | function createMarkup(HTMLString) { | 25 | function createMarkup(HTMLString) { |
24 | return { __html: HTMLString }; | 26 | return { __html: HTMLString }; |
@@ -189,9 +191,11 @@ class AppLayout extends Component { | |||
189 | <QuickSwitch /> | 191 | <QuickSwitch /> |
190 | {services} | 192 | {services} |
191 | {children} | 193 | {children} |
194 | <TrialStatusBar /> | ||
192 | </div> | 195 | </div> |
193 | <Todos /> | 196 | <Todos /> |
194 | </div> | 197 | </div> |
198 | <PlanSelection /> | ||
195 | </div> | 199 | </div> |
196 | </ErrorBoundary> | 200 | </ErrorBoundary> |
197 | ); | 201 | ); |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index f588449f4..83dc34a52 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -109,6 +109,7 @@ class AccountDashboard extends Component { | |||
109 | openBilling: PropTypes.func.isRequired, | 109 | openBilling: PropTypes.func.isRequired, |
110 | upgradeToPro: PropTypes.func.isRequired, | 110 | upgradeToPro: PropTypes.func.isRequired, |
111 | openInvoices: PropTypes.func.isRequired, | 111 | openInvoices: PropTypes.func.isRequired, |
112 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | ||
112 | }; | 113 | }; |
113 | 114 | ||
114 | static contextTypes = { | 115 | static contextTypes = { |
@@ -130,6 +131,7 @@ class AccountDashboard extends Component { | |||
130 | openBilling, | 131 | openBilling, |
131 | upgradeToPro, | 132 | upgradeToPro, |
132 | openInvoices, | 133 | openInvoices, |
134 | onCloseSubscriptionWindow, | ||
133 | } = this.props; | 135 | } = this.props; |
134 | const { intl } = this.context; | 136 | const { intl } = this.context; |
135 | 137 | ||
@@ -215,7 +217,9 @@ class AccountDashboard extends Component { | |||
215 | {intl.formatMessage(messages.yourLicense)} | 217 | {intl.formatMessage(messages.yourLicense)} |
216 | </H2> | 218 | </H2> |
217 | <p> | 219 | <p> |
218 | {isPremiumOverrideUser ? 'Franz Premium' : planName} | 220 | Franz |
221 | {' '} | ||
222 | {isPremiumOverrideUser ? 'Premium' : planName} | ||
219 | {user.team.isTrial && ( | 223 | {user.team.isTrial && ( |
220 | <> | 224 | <> |
221 | {' – '} | 225 | {' – '} |
@@ -238,14 +242,16 @@ class AccountDashboard extends Component { | |||
238 | </p> | 242 | </p> |
239 | </> | 243 | </> |
240 | )} | 244 | )} |
241 | <div className="manage-user-links"> | 245 | {!isProUser && ( |
242 | {!isProUser && ( | 246 | <div className="manage-user-links"> |
243 | <Button | 247 | <Button |
244 | label={intl.formatMessage(messages.upgradeAccountToPro)} | 248 | label={intl.formatMessage(messages.upgradeAccountToPro)} |
245 | className="franz-form__button--primary" | 249 | className="franz-form__button--primary" |
246 | onClick={upgradeToPro} | 250 | onClick={upgradeToPro} |
247 | /> | 251 | /> |
248 | )} | 252 | </div> |
253 | )} | ||
254 | <div className="manage-user-links"> | ||
249 | <Button | 255 | <Button |
250 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} | 256 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} |
251 | className="franz-form__button--inverted" | 257 | className="franz-form__button--inverted" |
@@ -263,7 +269,9 @@ class AccountDashboard extends Component { | |||
263 | {!user.isPremium && ( | 269 | {!user.isPremium && ( |
264 | <div className="account franz-form"> | 270 | <div className="account franz-form"> |
265 | <div className="account__box"> | 271 | <div className="account__box"> |
266 | <SubscriptionForm /> | 272 | <SubscriptionForm |
273 | onCloseWindow={onCloseSubscriptionWindow} | ||
274 | /> | ||
267 | </div> | 275 | </div> |
268 | </div> | 276 | </div> |
269 | )} | 277 | )} |
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index 49e73e569..192cfde7a 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js | |||
@@ -86,9 +86,9 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e | |||
86 | }, | 86 | }, |
87 | }); | 87 | }); |
88 | } | 88 | } |
89 | this.props.stores.user.isLoggingOut = true; | ||
89 | } | 90 | } |
90 | 91 | ||
91 | this.props.stores.user.isLoggingOut = true; | ||
92 | this.props.stores.router.push(isLoggedIn ? '/auth/logout' : '/auth/welcome'); | 92 | this.props.stores.router.push(isLoggedIn ? '/auth/logout' : '/auth/welcome'); |
93 | 93 | ||
94 | if (isLoggedIn) { | 94 | if (isLoggedIn) { |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 76138aa15..fa34ac60b 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -328,6 +328,18 @@ export default @observer class EditServiceForm extends Component { | |||
328 | )} | 328 | )} |
329 | </Tabs> | 329 | </Tabs> |
330 | )} | 330 | )} |
331 | |||
332 | {recipe.message && ( | ||
333 | <p | ||
334 | className="settings__message" | ||
335 | style={{ | ||
336 | marginTop: 0, | ||
337 | }} | ||
338 | > | ||
339 | <span className="mdi mdi-information" /> | ||
340 | {recipe.message} | ||
341 | </p> | ||
342 | )} | ||
331 | <div className="service-flex-grid"> | 343 | <div className="service-flex-grid"> |
332 | <div className="settings__options"> | 344 | <div className="settings__options"> |
333 | <div className="settings__settings-group"> | 345 | <div className="settings__settings-group"> |
@@ -417,13 +429,6 @@ export default @observer class EditServiceForm extends Component { | |||
417 | </div> | 429 | </div> |
418 | </PremiumFeatureContainer> | 430 | </PremiumFeatureContainer> |
419 | )} | 431 | )} |
420 | |||
421 | {recipe.message && ( | ||
422 | <p className="settings__message"> | ||
423 | <span className="mdi mdi-information" /> | ||
424 | {recipe.message} | ||
425 | </p> | ||
426 | )} | ||
427 | </form> | 432 | </form> |
428 | </div> | 433 | </div> |
429 | <div className="settings__controls"> | 434 | <div className="settings__controls"> |
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js index cdfbbe60d..ec486e5d0 100644 --- a/src/components/subscription/SubscriptionForm.js +++ b/src/components/subscription/SubscriptionForm.js | |||
@@ -30,11 +30,12 @@ const messages = defineMessages({ | |||
30 | 30 | ||
31 | const styles = () => ({ | 31 | const styles = () => ({ |
32 | activateTrialButton: { | 32 | activateTrialButton: { |
33 | margin: [40, 0, 50], | 33 | margin: [40, 'auto', 50], |
34 | display: 'flex', | ||
34 | }, | 35 | }, |
35 | }); | 36 | }); |
36 | 37 | ||
37 | export default @observer @injectSheet(styles) class SubscriptionForm extends Component { | 38 | export default @injectSheet(styles) @observer class SubscriptionForm extends Component { |
38 | static propTypes = { | 39 | static propTypes = { |
39 | selectPlan: PropTypes.func.isRequired, | 40 | selectPlan: PropTypes.func.isRequired, |
40 | isActivatingTrial: PropTypes.bool.isRequired, | 41 | isActivatingTrial: PropTypes.bool.isRequired, |
@@ -62,7 +63,6 @@ export default @observer @injectSheet(styles) class SubscriptionForm extends Com | |||
62 | className={classes.activateTrialButton} | 63 | className={classes.activateTrialButton} |
63 | busy={isActivatingTrial} | 64 | busy={isActivatingTrial} |
64 | onClick={selectPlan} | 65 | onClick={selectPlan} |
65 | stretch | ||
66 | /> | 66 | /> |
67 | <div className="subscription__premium-info"> | 67 | <div className="subscription__premium-info"> |
68 | <H3> | 68 | <H3> |
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js index 12ef8a6e9..12a51ad7b 100644 --- a/src/components/subscription/SubscriptionPopup.js +++ b/src/components/subscription/SubscriptionPopup.js | |||
@@ -59,8 +59,10 @@ export default @observer class SubscriptionPopup extends Component { | |||
59 | className="subscription-popup__webview" | 59 | className="subscription-popup__webview" |
60 | 60 | ||
61 | autosize | 61 | autosize |
62 | allowpopups | ||
62 | src={encodeURI(url)} | 63 | src={encodeURI(url)} |
63 | onDidNavigate={completeCheck} | 64 | onDidNavigate={completeCheck} |
65 | onDidNavigateInPage={completeCheck} | ||
64 | /> | 66 | /> |
65 | </div> | 67 | </div> |
66 | <div className="subscription-popup__toolbar franz-form"> | 68 | <div className="subscription-popup__toolbar franz-form"> |
diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js index 6ccdf20ae..d61b779ed 100644 --- a/src/components/subscription/TrialForm.js +++ b/src/components/subscription/TrialForm.js | |||
@@ -43,7 +43,8 @@ const messages = defineMessages({ | |||
43 | 43 | ||
44 | const styles = theme => ({ | 44 | const styles = theme => ({ |
45 | activateTrialButton: { | 45 | activateTrialButton: { |
46 | margin: [40, 0, 10], | 46 | margin: [40, 'auto', 10], |
47 | display: 'flex', | ||
47 | }, | 48 | }, |
48 | allOptionsButton: { | 49 | allOptionsButton: { |
49 | margin: [0, 0, 40], | 50 | margin: [0, 0, 40], |
@@ -93,7 +94,6 @@ export default @injectSheet(styles) @observer class TrialForm extends Component | |||
93 | className={classes.activateTrialButton} | 94 | className={classes.activateTrialButton} |
94 | busy={isActivatingTrial} | 95 | busy={isActivatingTrial} |
95 | onClick={activateTrial} | 96 | onClick={activateTrial} |
96 | stretch | ||
97 | /> | 97 | /> |
98 | <Button | 98 | <Button |
99 | label={intl.formatMessage(messages.allOptionsButton)} | 99 | label={intl.formatMessage(messages.allOptionsButton)} |
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js index 7c482c4d4..4926df470 100644 --- a/src/components/ui/FeatureItem.js +++ b/src/components/ui/FeatureItem.js | |||
@@ -10,6 +10,7 @@ const styles = theme => ({ | |||
10 | padding: [8, 0], | 10 | padding: [8, 0], |
11 | display: 'flex', | 11 | display: 'flex', |
12 | alignItems: 'center', | 12 | alignItems: 'center', |
13 | textAlign: 'left', | ||
13 | }, | 14 | }, |
14 | featureIcon: { | 15 | featureIcon: { |
15 | fill: theme.brandSuccess, | 16 | fill: theme.brandSuccess, |
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js index 62944ad75..7ba8b54d7 100644 --- a/src/components/ui/FeatureList.js +++ b/src/components/ui/FeatureList.js | |||
@@ -3,12 +3,33 @@ 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'; | ||
6 | 7 | ||
7 | const messages = defineMessages({ | 8 | const messages = defineMessages({ |
9 | availableRecipes: { | ||
10 | id: 'pricing.features.recipes', | ||
11 | defaultMessage: '!!!Choose from more than 70 Services', | ||
12 | }, | ||
13 | accountSync: { | ||
14 | id: 'pricing.features.accountSync', | ||
15 | defaultMessage: '!!!Account Synchronisation', | ||
16 | }, | ||
17 | desktopNotifications: { | ||
18 | id: 'pricing.features.desktopNotifications', | ||
19 | defaultMessage: '!!!Desktop Notifications', | ||
20 | }, | ||
8 | unlimitedServices: { | 21 | unlimitedServices: { |
9 | id: 'pricing.features.unlimitedServices', | 22 | id: 'pricing.features.unlimitedServices', |
10 | defaultMessage: '!!!Add unlimited services', | 23 | defaultMessage: '!!!Add unlimited services', |
11 | }, | 24 | }, |
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 | }, | ||
12 | spellchecker: { | 33 | spellchecker: { |
13 | id: 'pricing.features.spellchecker', | 34 | id: 'pricing.features.spellchecker', |
14 | defaultMessage: '!!!Spellchecker support', | 35 | defaultMessage: '!!!Spellchecker support', |
@@ -51,11 +72,13 @@ export class FeatureList extends Component { | |||
51 | static propTypes = { | 72 | static propTypes = { |
52 | className: PropTypes.string, | 73 | className: PropTypes.string, |
53 | featureClassName: PropTypes.string, | 74 | featureClassName: PropTypes.string, |
75 | plan: PropTypes.oneOf(PLANS), | ||
54 | }; | 76 | }; |
55 | 77 | ||
56 | static defaultProps = { | 78 | static defaultProps = { |
57 | className: '', | 79 | className: '', |
58 | featureClassName: '', | 80 | featureClassName: '', |
81 | plan: false, | ||
59 | } | 82 | } |
60 | 83 | ||
61 | static contextTypes = { | 84 | static contextTypes = { |
@@ -66,21 +89,52 @@ export class FeatureList extends Component { | |||
66 | const { | 89 | const { |
67 | className, | 90 | className, |
68 | featureClassName, | 91 | featureClassName, |
92 | plan, | ||
69 | } = this.props; | 93 | } = this.props; |
70 | const { intl } = this.context; | 94 | const { intl } = this.context; |
71 | 95 | ||
96 | const features = []; | ||
97 | if (plan === PLANS.FREE) { | ||
98 | features.push( | ||
99 | messages.upToThreeServices, | ||
100 | messages.availableRecipes, | ||
101 | messages.accountSync, | ||
102 | messages.desktopNotifications, | ||
103 | ); | ||
104 | } else if (plan === PLANS.PERSONAL) { | ||
105 | features.push( | ||
106 | messages.upToSixServices, | ||
107 | messages.spellchecker, | ||
108 | messages.appDelays, | ||
109 | messages.adFree, | ||
110 | ); | ||
111 | } else if (plan === PLANS.PRO) { | ||
112 | features.push( | ||
113 | messages.unlimitedServices, | ||
114 | messages.workspaces, | ||
115 | messages.customWebsites, | ||
116 | // messages.onPremise, | ||
117 | messages.thirdPartyServices, | ||
118 | // messages.serviceProxies, | ||
119 | ); | ||
120 | } else { | ||
121 | features.push( | ||
122 | messages.unlimitedServices, | ||
123 | messages.spellchecker, | ||
124 | messages.workspaces, | ||
125 | messages.customWebsites, | ||
126 | messages.onPremise, | ||
127 | messages.thirdPartyServices, | ||
128 | messages.serviceProxies, | ||
129 | messages.teamManagement, | ||
130 | messages.appDelays, | ||
131 | messages.adFree, | ||
132 | ); | ||
133 | } | ||
134 | |||
72 | return ( | 135 | return ( |
73 | <ul className={className}> | 136 | <ul className={className}> |
74 | <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} className={featureClassName} /> | 137 | {features.map(feature => <FeatureItem name={intl.formatMessage(feature)} className={featureClassName} />)} |
75 | <FeatureItem name={intl.formatMessage(messages.spellchecker)} className={featureClassName} /> | ||
76 | <FeatureItem name={intl.formatMessage(messages.workspaces)} className={featureClassName} /> | ||
77 | <FeatureItem name={intl.formatMessage(messages.customWebsites)} className={featureClassName} /> | ||
78 | <FeatureItem name={intl.formatMessage(messages.onPremise)} className={featureClassName} /> | ||
79 | <FeatureItem name={intl.formatMessage(messages.thirdPartyServices)} className={featureClassName} /> | ||
80 | <FeatureItem name={intl.formatMessage(messages.serviceProxies)} className={featureClassName} /> | ||
81 | <FeatureItem name={intl.formatMessage(messages.teamManagement)} className={featureClassName} /> | ||
82 | <FeatureItem name={intl.formatMessage(messages.appDelays)} className={featureClassName} /> | ||
83 | <FeatureItem name={intl.formatMessage(messages.adFree)} className={featureClassName} /> | ||
84 | </ul> | 138 | </ul> |
85 | ); | 139 | ); |
86 | } | 140 | } |
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js index 7ba353be3..36bf38c98 100644 --- a/src/components/ui/PremiumFeatureContainer/index.js +++ b/src/components/ui/PremiumFeatureContainer/index.js | |||
@@ -9,7 +9,7 @@ import { oneOrManyChildElements } from '../../../prop-types'; | |||
9 | import UserStore from '../../../stores/UserStore'; | 9 | import UserStore from '../../../stores/UserStore'; |
10 | 10 | ||
11 | import styles from './styles'; | 11 | import styles from './styles'; |
12 | import FeatureStore from '../../../stores/FeaturesStore'; | 12 | import FeaturesStore from '../../../stores/FeaturesStore'; |
13 | 13 | ||
14 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
15 | action: { | 15 | action: { |
@@ -90,7 +90,7 @@ PremiumFeatureContainer.wrappedComponent.propTypes = { | |||
90 | children: oneOrManyChildElements.isRequired, | 90 | children: oneOrManyChildElements.isRequired, |
91 | stores: PropTypes.shape({ | 91 | stores: PropTypes.shape({ |
92 | user: PropTypes.instanceOf(UserStore).isRequired, | 92 | user: PropTypes.instanceOf(UserStore).isRequired, |
93 | features: PropTypes.instanceOf(FeatureStore).isRequired, | 93 | features: PropTypes.instanceOf(FeaturesStore).isRequired, |
94 | }).isRequired, | 94 | }).isRequired, |
95 | actions: PropTypes.shape({ | 95 | actions: PropTypes.shape({ |
96 | ui: PropTypes.shape({ | 96 | ui: PropTypes.shape({ |
diff --git a/src/config.js b/src/config.js index e762f879f..761d26eea 100644 --- a/src/config.js +++ b/src/config.js | |||
@@ -125,10 +125,10 @@ export const ALLOWED_PROTOCOLS = [ | |||
125 | ]; | 125 | ]; |
126 | 126 | ||
127 | export const PLANS = { | 127 | export const PLANS = { |
128 | PERSONAL: 'PERSONAL', | 128 | PERSONAL: 'personal', |
129 | PRO: 'PRO', | 129 | PRO: 'pro', |
130 | LEGACY: 'LEGACY', | 130 | LEGACY: 'legacy', |
131 | FREE: 'FREE', | 131 | FREE: 'free', |
132 | }; | 132 | }; |
133 | 133 | ||
134 | export const PLANS_MAPPING = { | 134 | export const PLANS_MAPPING = { |
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index af1651931..21c859c12 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js | |||
@@ -20,14 +20,19 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend | |||
20 | } = this.props; | 20 | } = this.props; |
21 | 21 | ||
22 | const { activateTrialRequest } = stores.user; | 22 | const { activateTrialRequest } = stores.user; |
23 | const { defaultTrialPlan } = stores.features.features; | 23 | const { defaultTrialPlan, canSkipTrial } = stores.features.anonymousFeatures; |
24 | 24 | ||
25 | actions.user.activateTrial({ planId: defaultTrialPlan }); | 25 | if (!canSkipTrial) { |
26 | await activateTrialRequest._promise; | ||
27 | |||
28 | if (!activateTrialRequest.isError) { | ||
29 | stores.router.push('/'); | 26 | stores.router.push('/'); |
30 | stores.user.hasCompletedSignup = true; | 27 | stores.user.hasCompletedSignup = true; |
28 | } else { | ||
29 | actions.user.activateTrial({ planId: defaultTrialPlan }); | ||
30 | await activateTrialRequest._promise; | ||
31 | |||
32 | if (!activateTrialRequest.isError) { | ||
33 | stores.router.push('/'); | ||
34 | stores.user.hasCompletedSignup = true; | ||
35 | } | ||
31 | } | 36 | } |
32 | } | 37 | } |
33 | 38 | ||
@@ -37,8 +42,17 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend | |||
37 | stores, | 42 | stores, |
38 | } = this.props; | 43 | } = this.props; |
39 | 44 | ||
40 | const { getUserInfoRequest, activateTrialRequest } = stores.user; | 45 | const { getUserInfoRequest, activateTrialRequest, data } = stores.user; |
41 | const { featuresRequest } = stores.features; | 46 | const { featuresRequest, features } = stores.features; |
47 | |||
48 | const { pricingConfig } = features; | ||
49 | |||
50 | let currency = '$'; | ||
51 | let price = 5.99; | ||
52 | if (pricingConfig) { | ||
53 | ({ currency } = pricingConfig); | ||
54 | ({ price } = pricingConfig.plans.pro.yearly); | ||
55 | } | ||
42 | 56 | ||
43 | return ( | 57 | return ( |
44 | <Pricing | 58 | <Pricing |
@@ -46,7 +60,11 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend | |||
46 | isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)} | 60 | isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)} |
47 | isActivatingTrial={activateTrialRequest.isExecuting} | 61 | isActivatingTrial={activateTrialRequest.isExecuting} |
48 | trialActivationError={activateTrialRequest.isError} | 62 | trialActivationError={activateTrialRequest.isError} |
63 | canSkipTrial={features.canSkipTrial} | ||
49 | error={error} | 64 | error={error} |
65 | currency={currency} | ||
66 | price={price} | ||
67 | name={data.firstname} | ||
50 | /> | 68 | /> |
51 | ); | 69 | ); |
52 | } | 70 | } |
diff --git a/src/containers/auth/SignupScreen.js b/src/containers/auth/SignupScreen.js index efc7ea4c1..f93498be2 100644 --- a/src/containers/auth/SignupScreen.js +++ b/src/containers/auth/SignupScreen.js | |||
@@ -4,6 +4,7 @@ import { inject, observer } from 'mobx-react'; | |||
4 | 4 | ||
5 | import Signup from '../../components/auth/Signup'; | 5 | import Signup from '../../components/auth/Signup'; |
6 | import UserStore from '../../stores/UserStore'; | 6 | import UserStore from '../../stores/UserStore'; |
7 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
7 | 8 | ||
8 | import { globalError as globalErrorPropType } from '../../prop-types'; | 9 | import { globalError as globalErrorPropType } from '../../prop-types'; |
9 | 10 | ||
@@ -12,11 +13,27 @@ export default @inject('stores', 'actions') @observer class SignupScreen extends | |||
12 | error: globalErrorPropType.isRequired, | 13 | error: globalErrorPropType.isRequired, |
13 | }; | 14 | }; |
14 | 15 | ||
16 | onSignup(values) { | ||
17 | const { actions, stores } = this.props; | ||
18 | |||
19 | const { canSkipTrial, defaultTrialPlan, pricingConfig } = stores.features.anonymousFeatures; | ||
20 | |||
21 | if (!canSkipTrial) { | ||
22 | Object.assign(values, { | ||
23 | plan: defaultTrialPlan, | ||
24 | currency: pricingConfig.currencyID, | ||
25 | }); | ||
26 | } | ||
27 | |||
28 | actions.user.signup(values); | ||
29 | } | ||
30 | |||
15 | render() { | 31 | render() { |
16 | const { actions, stores, error } = this.props; | 32 | const { stores, error } = this.props; |
33 | |||
17 | return ( | 34 | return ( |
18 | <Signup | 35 | <Signup |
19 | onSubmit={actions.user.signup} | 36 | onSubmit={values => this.onSignup(values)} |
20 | isSubmitting={stores.user.signupRequest.isExecuting} | 37 | isSubmitting={stores.user.signupRequest.isExecuting} |
21 | loginRoute={stores.user.loginRoute} | 38 | loginRoute={stores.user.loginRoute} |
22 | error={error} | 39 | error={error} |
@@ -33,5 +50,6 @@ SignupScreen.wrappedComponent.propTypes = { | |||
33 | }).isRequired, | 50 | }).isRequired, |
34 | stores: PropTypes.shape({ | 51 | stores: PropTypes.shape({ |
35 | user: PropTypes.instanceOf(UserStore).isRequired, | 52 | user: PropTypes.instanceOf(UserStore).isRequired, |
53 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
36 | }).isRequired, | 54 | }).isRequired, |
37 | }; | 55 | }; |
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 95fbd109f..a4312d9de 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -24,6 +24,7 @@ import { state as delayAppState } from '../../features/delayApp'; | |||
24 | import { workspaceActions } from '../../features/workspaces/actions'; | 24 | import { workspaceActions } from '../../features/workspaces/actions'; |
25 | import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; | 25 | import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; |
26 | import { workspaceStore } from '../../features/workspaces'; | 26 | import { workspaceStore } from '../../features/workspaces'; |
27 | import WorkspacesStore from '../../features/workspaces/store'; | ||
27 | 28 | ||
28 | export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { | 29 | export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { |
29 | static defaultProps = { | 30 | static defaultProps = { |
@@ -41,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
41 | globalError, | 42 | globalError, |
42 | requests, | 43 | requests, |
43 | user, | 44 | user, |
45 | workspaces, | ||
44 | } = this.props.stores; | 46 | } = this.props.stores; |
45 | 47 | ||
46 | const { | 48 | const { |
@@ -79,7 +81,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
79 | const isLoadingServices = services.allServicesRequest.isExecuting | 81 | const isLoadingServices = services.allServicesRequest.isExecuting |
80 | && services.allServicesRequest.isExecutingFirstTime; | 82 | && services.allServicesRequest.isExecutingFirstTime; |
81 | 83 | ||
82 | if (isLoadingFeatures || isLoadingServices) { | 84 | if (isLoadingFeatures || isLoadingServices || workspaces.isLoadingWorkspaces) { |
83 | return ( | 85 | return ( |
84 | <ThemeProvider theme={ui.theme}> | 86 | <ThemeProvider theme={ui.theme}> |
85 | <AppLoader /> | 87 | <AppLoader /> |
@@ -175,6 +177,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { | |||
175 | user: PropTypes.instanceOf(UserStore).isRequired, | 177 | user: PropTypes.instanceOf(UserStore).isRequired, |
176 | requests: PropTypes.instanceOf(RequestStore).isRequired, | 178 | requests: PropTypes.instanceOf(RequestStore).isRequired, |
177 | globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, | 179 | globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, |
180 | workspaces: PropTypes.instanceOf(WorkspacesStore).isRequired, | ||
178 | }).isRequired, | 181 | }).isRequired, |
179 | actions: PropTypes.shape({ | 182 | actions: PropTypes.shape({ |
180 | service: PropTypes.shape({ | 183 | service: PropTypes.shape({ |
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index 41cdeb79a..93ab44690 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -5,6 +5,7 @@ import { inject, observer } from 'mobx-react'; | |||
5 | import PaymentStore from '../../stores/PaymentStore'; | 5 | import PaymentStore from '../../stores/PaymentStore'; |
6 | import UserStore from '../../stores/UserStore'; | 6 | import UserStore from '../../stores/UserStore'; |
7 | import AppStore from '../../stores/AppStore'; | 7 | import AppStore from '../../stores/AppStore'; |
8 | import FeaturesStore from '../../stores/FeaturesStore'; | ||
8 | 9 | ||
9 | import AccountDashboard from '../../components/settings/account/AccountDashboard'; | 10 | import AccountDashboard from '../../components/settings/account/AccountDashboard'; |
10 | import ErrorBoundary from '../../components/util/ErrorBoundary'; | 11 | import ErrorBoundary from '../../components/util/ErrorBoundary'; |
@@ -12,8 +13,9 @@ import { WEBSITE } from '../../environment'; | |||
12 | 13 | ||
13 | export default @inject('stores', 'actions') @observer class AccountScreen extends Component { | 14 | export default @inject('stores', 'actions') @observer class AccountScreen extends Component { |
14 | onCloseWindow() { | 15 | onCloseWindow() { |
15 | const { user } = this.props.stores; | 16 | const { user, features } = this.props.stores; |
16 | user.getUserInfoRequest.invalidate({ immediately: true }); | 17 | user.getUserInfoRequest.invalidate({ immediately: true }); |
18 | features.featuresRequest.invalidate({ immediately: true }); | ||
17 | } | 19 | } |
18 | 20 | ||
19 | reloadData() { | 21 | reloadData() { |
@@ -39,12 +41,17 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend | |||
39 | } | 41 | } |
40 | 42 | ||
41 | render() { | 43 | render() { |
42 | const { user, payment } = this.props.stores; | 44 | const { user, payment, features } = this.props.stores; |
43 | const { user: userActions } = this.props.actions; | 45 | const { |
46 | user: userActions, | ||
47 | payment: paymentActions, | ||
48 | } = this.props.actions; | ||
44 | 49 | ||
45 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; | 50 | const isLoadingUserInfo = user.getUserInfoRequest.isExecuting; |
46 | const isLoadingPlans = payment.plansRequest.isExecuting; | 51 | const isLoadingPlans = payment.plansRequest.isExecuting; |
47 | 52 | ||
53 | const { upgradeAccount } = paymentActions; | ||
54 | |||
48 | return ( | 55 | return ( |
49 | <ErrorBoundary> | 56 | <ErrorBoundary> |
50 | <AccountDashboard | 57 | <AccountDashboard |
@@ -60,7 +67,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend | |||
60 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} | 67 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} |
61 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} | 68 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} |
62 | openEditAccount={() => this.handleWebsiteLink('/user/profile')} | 69 | openEditAccount={() => this.handleWebsiteLink('/user/profile')} |
63 | upgradeToPro={() => this.handleWebsiteLink('/inapp/user/licenses')} | 70 | upgradeToPro={() => upgradeAccount({ planId: features.features.pricingConfig.plans.pro.yearly.id })} |
64 | openBilling={() => this.handleWebsiteLink('/user/billing')} | 71 | openBilling={() => this.handleWebsiteLink('/user/billing')} |
65 | openInvoices={() => this.handleWebsiteLink('/user/invoices')} | 72 | openInvoices={() => this.handleWebsiteLink('/user/invoices')} |
66 | /> | 73 | /> |
@@ -72,12 +79,14 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend | |||
72 | AccountScreen.wrappedComponent.propTypes = { | 79 | AccountScreen.wrappedComponent.propTypes = { |
73 | stores: PropTypes.shape({ | 80 | stores: PropTypes.shape({ |
74 | user: PropTypes.instanceOf(UserStore).isRequired, | 81 | user: PropTypes.instanceOf(UserStore).isRequired, |
82 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
75 | payment: PropTypes.instanceOf(PaymentStore).isRequired, | 83 | payment: PropTypes.instanceOf(PaymentStore).isRequired, |
76 | app: PropTypes.instanceOf(AppStore).isRequired, | 84 | app: PropTypes.instanceOf(AppStore).isRequired, |
77 | }).isRequired, | 85 | }).isRequired, |
78 | actions: PropTypes.shape({ | 86 | actions: PropTypes.shape({ |
79 | payment: PropTypes.shape({ | 87 | payment: PropTypes.shape({ |
80 | createDashboardUrl: PropTypes.func.isRequired, | 88 | createDashboardUrl: PropTypes.func.isRequired, |
89 | upgradeAccount: PropTypes.func.isRequired, | ||
81 | }).isRequired, | 90 | }).isRequired, |
82 | app: PropTypes.shape({ | 91 | app: PropTypes.shape({ |
83 | openExternalUrl: PropTypes.func.isRequired, | 92 | openExternalUrl: PropTypes.func.isRequired, |
diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js index 726b10628..38e46a7ba 100644 --- a/src/containers/subscription/SubscriptionFormScreen.js +++ b/src/containers/subscription/SubscriptionFormScreen.js | |||
@@ -1,4 +1,5 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import { remote } from 'electron'; | ||
2 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
3 | import { inject, observer } from 'mobx-react'; | 4 | import { inject, observer } from 'mobx-react'; |
4 | 5 | ||
@@ -7,11 +8,21 @@ import PaymentStore from '../../stores/PaymentStore'; | |||
7 | import SubscriptionForm from '../../components/subscription/SubscriptionForm'; | 8 | import SubscriptionForm from '../../components/subscription/SubscriptionForm'; |
8 | import TrialForm from '../../components/subscription/TrialForm'; | 9 | import TrialForm from '../../components/subscription/TrialForm'; |
9 | 10 | ||
11 | const { BrowserWindow } = remote; | ||
12 | |||
10 | export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { | 13 | export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { |
14 | static propTypes = { | ||
15 | onCloseWindow: PropTypes.func, | ||
16 | } | ||
17 | |||
18 | static defaultProps = { | ||
19 | onCloseWindow: () => null, | ||
20 | } | ||
21 | |||
11 | async openBrowser() { | 22 | async openBrowser() { |
12 | const { | 23 | const { |
13 | actions, | ||
14 | stores, | 24 | stores, |
25 | onCloseWindow, | ||
15 | } = this.props; | 26 | } = this.props; |
16 | 27 | ||
17 | const { | 28 | const { |
@@ -22,7 +33,24 @@ export default @inject('stores', 'actions') @observer class SubscriptionFormScre | |||
22 | let hostedPageURL = features.features.planSelectionURL; | 33 | let hostedPageURL = features.features.planSelectionURL; |
23 | hostedPageURL = user.getAuthURL(hostedPageURL); | 34 | hostedPageURL = user.getAuthURL(hostedPageURL); |
24 | 35 | ||
25 | actions.app.openExternalUrl({ url: hostedPageURL }); | 36 | const paymentWindow = new BrowserWindow({ |
37 | parent: remote.getCurrentWindow(), | ||
38 | modal: true, | ||
39 | title: '🔒 Franz Supporter License', | ||
40 | width: 800, | ||
41 | height: window.innerHeight - 100, | ||
42 | maxWidth: 800, | ||
43 | minWidth: 600, | ||
44 | webPreferences: { | ||
45 | nodeIntegration: true, | ||
46 | webviewTag: true, | ||
47 | }, | ||
48 | }); | ||
49 | paymentWindow.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPageURL)}`); | ||
50 | |||
51 | paymentWindow.on('closed', () => { | ||
52 | onCloseWindow(); | ||
53 | }); | ||
26 | } | 54 | } |
27 | 55 | ||
28 | render() { | 56 | render() { |
diff --git a/src/electron/Settings.js b/src/electron/Settings.js index 95e3dcfa4..d4f0d25bf 100644 --- a/src/electron/Settings.js +++ b/src/electron/Settings.js | |||
@@ -47,7 +47,9 @@ export default class Settings { | |||
47 | } | 47 | } |
48 | 48 | ||
49 | _writeFile() { | 49 | _writeFile() { |
50 | outputJsonSync(this.settingsFile, this.store); | 50 | outputJsonSync(this.settingsFile, this.store, { |
51 | spaces: 2, | ||
52 | }); | ||
51 | debug('Write settings file', this.type, toJS(this.store)); | 53 | debug('Write settings file', this.type, toJS(this.store)); |
52 | } | 54 | } |
53 | 55 | ||
diff --git a/src/electron/ipc-api/localServer.js b/src/electron/ipc-api/localServer.js index 2f8f1020a..d12fb5708 100644 --- a/src/electron/ipc-api/localServer.js +++ b/src/electron/ipc-api/localServer.js | |||
@@ -1,5 +1,4 @@ | |||
1 | import { ipcMain, app } from 'electron'; | 1 | import { ipcMain, app } from 'electron'; |
2 | import path from 'path'; | ||
3 | import net from 'net'; | 2 | import net from 'net'; |
4 | import startServer from '../../server/start'; | 3 | import startServer from '../../server/start'; |
5 | 4 | ||
@@ -38,7 +37,7 @@ export default (params) => { | |||
38 | console.log('Starting local server on port', port); | 37 | console.log('Starting local server on port', port); |
39 | 38 | ||
40 | startServer( | 39 | startServer( |
41 | path.join(app.getPath('userData'), 'server.sqlite'), | 40 | app.getPath('userData'), |
42 | port, | 41 | port, |
43 | ); | 42 | ); |
44 | 43 | ||
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js index c61cb06c9..81f89bc52 100644 --- a/src/features/delayApp/Component.js +++ b/src/features/delayApp/Component.js | |||
@@ -21,7 +21,7 @@ const messages = defineMessages({ | |||
21 | }, | 21 | }, |
22 | action: { | 22 | action: { |
23 | id: 'feature.delayApp.upgrade.action', | 23 | id: 'feature.delayApp.upgrade.action', |
24 | defaultMessage: '!!!Get a Franz Supporter License', | 24 | defaultMessage: '!!!Upgrade Franz', |
25 | }, | 25 | }, |
26 | actionTrial: { | 26 | actionTrial: { |
27 | id: 'feature.delayApp.trial.action', | 27 | id: 'feature.delayApp.trial.action', |
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js index 5cc6c9506..51bd887a2 100644 --- a/src/features/delayApp/index.js +++ b/src/features/delayApp/index.js | |||
@@ -3,6 +3,7 @@ import moment from 'moment'; | |||
3 | import DelayAppComponent from './Component'; | 3 | import DelayAppComponent from './Component'; |
4 | 4 | ||
5 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; | 5 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; |
6 | import { getUserWorkspacesRequest } from '../workspaces/api'; | ||
6 | 7 | ||
7 | const debug = require('debug')('Ferdi:feature:delayApp'); | 8 | const debug = require('debug')('Ferdi:feature:delayApp'); |
8 | 9 | ||
@@ -32,7 +33,13 @@ export default function init(stores) { | |||
32 | }; | 33 | }; |
33 | 34 | ||
34 | reaction( | 35 | reaction( |
35 | () => stores.user.isLoggedIn && stores.services.allServicesRequest.wasExecuted && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, | 36 | () => ( |
37 | stores.user.isLoggedIn | ||
38 | && stores.services.allServicesRequest.wasExecuted | ||
39 | && getUserWorkspacesRequest.wasExecuted | ||
40 | && stores.features.features.needToWaitToProceed | ||
41 | && !stores.user.data.isPremium | ||
42 | ), | ||
36 | (isEnabled) => { | 43 | (isEnabled) => { |
37 | if (isEnabled) { | 44 | if (isEnabled) { |
38 | debug('Enabling `delayApp` feature'); | 45 | debug('Enabling `delayApp` feature'); |
diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js new file mode 100644 index 000000000..83f58bfd7 --- /dev/null +++ b/src/features/planSelection/actions.js | |||
@@ -0,0 +1,9 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | ||
3 | |||
4 | export const planSelectionActions = createActionsFromDefinitions({ | ||
5 | downgradeAccount: {}, | ||
6 | hideOverlay: {}, | ||
7 | }, PropTypes.checkPropTypes); | ||
8 | |||
9 | export default planSelectionActions; | ||
diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js new file mode 100644 index 000000000..734643f10 --- /dev/null +++ b/src/features/planSelection/api.js | |||
@@ -0,0 +1,26 @@ | |||
1 | import { sendAuthRequest } from '../../api/utils/auth'; | ||
2 | import { API, API_VERSION } from '../../environment'; | ||
3 | import Request from '../../stores/lib/Request'; | ||
4 | |||
5 | const debug = require('debug')('Franz:feature:planSelection:api'); | ||
6 | |||
7 | export const planSelectionApi = { | ||
8 | downgrade: async () => { | ||
9 | const url = `${API}/${API_VERSION}/payment/downgrade`; | ||
10 | const options = { | ||
11 | method: 'PUT', | ||
12 | }; | ||
13 | debug('downgrade UPDATE', url, options); | ||
14 | const result = await sendAuthRequest(url, options); | ||
15 | debug('downgrade RESULT', result); | ||
16 | if (!result.ok) throw result; | ||
17 | |||
18 | return result.ok; | ||
19 | }, | ||
20 | }; | ||
21 | |||
22 | export const downgradeUserRequest = new Request(planSelectionApi, 'downgrade'); | ||
23 | |||
24 | export const resetApiRequests = () => { | ||
25 | downgradeUserRequest.reset(); | ||
26 | }; | ||
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js new file mode 100644 index 000000000..ec061377b --- /dev/null +++ b/src/features/planSelection/components/PlanItem.js | |||
@@ -0,0 +1,207 @@ | |||
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 classnames from 'classnames'; | ||
7 | import color from 'color'; | ||
8 | |||
9 | import { H2 } from '@meetfranz/ui'; | ||
10 | |||
11 | import { Button } from '@meetfranz/forms'; | ||
12 | import { mdiArrowRight } from '@mdi/js'; | ||
13 | |||
14 | const messages = defineMessages({ | ||
15 | perMonth: { | ||
16 | id: 'subscription.interval.perMonth', | ||
17 | defaultMessage: '!!!per month', | ||
18 | }, | ||
19 | perMonthPerUser: { | ||
20 | id: 'subscription.interval.perMonthPerUser', | ||
21 | defaultMessage: '!!!per month & user', | ||
22 | }, | ||
23 | bestValue: { | ||
24 | id: 'subscription.bestValue', | ||
25 | defaultMessage: '!!!Best value', | ||
26 | }, | ||
27 | }); | ||
28 | |||
29 | const styles = theme => ({ | ||
30 | root: { | ||
31 | display: 'flex', | ||
32 | flexDirection: 'column', | ||
33 | borderRadius: theme.borderRadius, | ||
34 | flex: 1, | ||
35 | color: theme.styleTypes.primary.accent, | ||
36 | overflow: 'hidden', | ||
37 | textAlign: 'center', | ||
38 | |||
39 | '& h2': { | ||
40 | textAlign: 'center', | ||
41 | marginBottom: 10, | ||
42 | fontSize: 30, | ||
43 | color: theme.styleTypes.primary.contrast, | ||
44 | }, | ||
45 | }, | ||
46 | currency: { | ||
47 | fontSize: 35, | ||
48 | }, | ||
49 | priceWrapper: { | ||
50 | height: 50, | ||
51 | marginBottom: 0, | ||
52 | }, | ||
53 | price: { | ||
54 | fontSize: 50, | ||
55 | |||
56 | '& sup': { | ||
57 | fontSize: 20, | ||
58 | verticalAlign: 20, | ||
59 | }, | ||
60 | }, | ||
61 | text: { | ||
62 | marginBottom: 'auto', | ||
63 | }, | ||
64 | cta: { | ||
65 | background: theme.styleTypes.primary.accent, | ||
66 | color: theme.styleTypes.primary.contrast, | ||
67 | margin: [40, 'auto', 0, 'auto'], | ||
68 | }, | ||
69 | divider: { | ||
70 | width: 40, | ||
71 | border: 0, | ||
72 | borderTop: [1, 'solid', theme.styleTypes.primary.contrast], | ||
73 | margin: [15, 'auto', 20], | ||
74 | }, | ||
75 | header: { | ||
76 | padding: 20, | ||
77 | background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), | ||
78 | color: theme.styleTypes.primary.contrast, | ||
79 | position: 'relative', | ||
80 | }, | ||
81 | content: { | ||
82 | padding: [10, 20, 20], | ||
83 | background: '#EFEFEF', | ||
84 | }, | ||
85 | simpleCTA: { | ||
86 | background: 'none', | ||
87 | color: theme.styleTypes.primary.accent, | ||
88 | |||
89 | '& svg': { | ||
90 | fill: theme.styleTypes.primary.accent, | ||
91 | }, | ||
92 | }, | ||
93 | bestValue: { | ||
94 | background: theme.styleTypes.success.accent, | ||
95 | color: theme.styleTypes.success.contrast, | ||
96 | right: -66, | ||
97 | top: -40, | ||
98 | height: 'auto', | ||
99 | position: 'absolute', | ||
100 | transform: 'rotateZ(45deg)', | ||
101 | textAlign: 'center', | ||
102 | padding: [5, 50], | ||
103 | transformOrigin: 'left bottom', | ||
104 | fontSize: 12, | ||
105 | boxShadow: '0 2px 6px rgba(0,0,0,0.15)', | ||
106 | }, | ||
107 | }); | ||
108 | |||
109 | |||
110 | export default @observer @injectSheet(styles) class PlanItem extends Component { | ||
111 | static propTypes = { | ||
112 | name: PropTypes.string.isRequired, | ||
113 | text: PropTypes.string.isRequired, | ||
114 | price: PropTypes.number.isRequired, | ||
115 | currency: PropTypes.string.isRequired, | ||
116 | upgrade: PropTypes.func.isRequired, | ||
117 | ctaLabel: PropTypes.string.isRequired, | ||
118 | simpleCTA: PropTypes.bool, | ||
119 | perUser: PropTypes.bool, | ||
120 | classes: PropTypes.object.isRequired, | ||
121 | bestValue: PropTypes.bool, | ||
122 | className: PropTypes.string, | ||
123 | children: PropTypes.element, | ||
124 | }; | ||
125 | |||
126 | static defaultProps = { | ||
127 | simpleCTA: false, | ||
128 | perUser: false, | ||
129 | children: null, | ||
130 | bestValue: false, | ||
131 | className: '', | ||
132 | } | ||
133 | |||
134 | static contextTypes = { | ||
135 | intl: intlShape, | ||
136 | }; | ||
137 | |||
138 | render() { | ||
139 | const { | ||
140 | name, | ||
141 | text, | ||
142 | price, | ||
143 | currency, | ||
144 | classes, | ||
145 | upgrade, | ||
146 | ctaLabel, | ||
147 | simpleCTA, | ||
148 | perUser, | ||
149 | bestValue, | ||
150 | className, | ||
151 | children, | ||
152 | } = this.props; | ||
153 | const { intl } = this.context; | ||
154 | |||
155 | const priceParts = `${price}`.split('.'); | ||
156 | |||
157 | return ( | ||
158 | <div className={classnames({ | ||
159 | [classes.root]: true, | ||
160 | [className]: className, | ||
161 | })} | ||
162 | > | ||
163 | <div className={classes.header}> | ||
164 | {bestValue && ( | ||
165 | <div className={classes.bestValue}> | ||
166 | {intl.formatMessage(messages.bestValue)} | ||
167 | </div> | ||
168 | )} | ||
169 | <H2 className={classes.planName}>{name}</H2> | ||
170 | <p className={classes.text}> | ||
171 | {text} | ||
172 | </p> | ||
173 | <hr className={classes.divider} /> | ||
174 | <p className={classes.priceWrapper}> | ||
175 | <span className={classes.currency}>{currency}</span> | ||
176 | <span className={classes.price}> | ||
177 | {priceParts[0]} | ||
178 | <sup>{priceParts[1]}</sup> | ||
179 | </span> | ||
180 | </p> | ||
181 | <p className={classes.interval}> | ||
182 | {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)} | ||
183 | </p> | ||
184 | </div> | ||
185 | |||
186 | <div className={classes.content}> | ||
187 | {children} | ||
188 | |||
189 | <Button | ||
190 | className={classnames({ | ||
191 | [classes.cta]: true, | ||
192 | [classes.simpleCTA]: simpleCTA, | ||
193 | })} | ||
194 | icon={simpleCTA ? mdiArrowRight : null} | ||
195 | label={( | ||
196 | <> | ||
197 | {ctaLabel} | ||
198 | </> | ||
199 | )} | ||
200 | onClick={upgrade} | ||
201 | /> | ||
202 | </div> | ||
203 | |||
204 | </div> | ||
205 | ); | ||
206 | } | ||
207 | } | ||
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js new file mode 100644 index 000000000..4bf5238dd --- /dev/null +++ b/src/features/planSelection/components/PlanSelection.js | |||
@@ -0,0 +1,279 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import { H1, H2, Icon } from '@meetfranz/ui'; | ||
7 | import color from 'color'; | ||
8 | |||
9 | import { mdiRocket, mdiArrowRight } from '@mdi/js'; | ||
10 | import PlanItem from './PlanItem'; | ||
11 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
12 | import { PLANS } from '../../../config'; | ||
13 | import { FeatureList } from '../../../components/ui/FeatureList'; | ||
14 | import Appear from '../../../components/ui/effects/Appear'; | ||
15 | |||
16 | const messages = defineMessages({ | ||
17 | welcome: { | ||
18 | id: 'feature.planSelection.fullscreen.welcome', | ||
19 | defaultMessage: '!!!Are you ready to choose, {name}', | ||
20 | }, | ||
21 | subheadline: { | ||
22 | id: 'feature.planSelection.fullscreen.subheadline', | ||
23 | defaultMessage: '!!!It\'s time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.', | ||
24 | }, | ||
25 | textFree: { | ||
26 | id: 'feature.planSelection.free.text', | ||
27 | defaultMessage: '!!!Basic functionality', | ||
28 | }, | ||
29 | textPersonal: { | ||
30 | id: 'feature.planSelection.personal.text', | ||
31 | defaultMessage: '!!!More services, no waiting - ideal for personal use.', | ||
32 | }, | ||
33 | textProfessional: { | ||
34 | id: 'feature.planSelection.pro.text', | ||
35 | defaultMessage: '!!!Unlimited services and professional features for you - and your team.', | ||
36 | }, | ||
37 | ctaStayOnFree: { | ||
38 | id: 'feature.planSelection.cta.stayOnFree', | ||
39 | defaultMessage: '!!!Stay on Free', | ||
40 | }, | ||
41 | ctaDowngradeFree: { | ||
42 | id: 'feature.planSelection.cta.ctaDowngradeFree', | ||
43 | defaultMessage: '!!!Downgrade to Free', | ||
44 | }, | ||
45 | actionTrial: { | ||
46 | id: 'feature.planSelection.cta.trial', | ||
47 | defaultMessage: '!!!Start my free 14-days Trial', | ||
48 | }, | ||
49 | shortActionPersonal: { | ||
50 | id: 'feature.planSelection.cta.upgradePersonal', | ||
51 | defaultMessage: '!!!Choose Personal', | ||
52 | }, | ||
53 | shortActionPro: { | ||
54 | id: 'feature.planSelection.cta.upgradePro', | ||
55 | defaultMessage: '!!!Choose Professional', | ||
56 | }, | ||
57 | fullFeatureList: { | ||
58 | id: 'feature.planSelection.fullFeatureList', | ||
59 | defaultMessage: '!!!Complete comparison of all plans', | ||
60 | }, | ||
61 | pricesBasedOnAnnualPayment: { | ||
62 | id: 'feature.planSelection.pricesBasedOnAnnualPayment', | ||
63 | defaultMessage: '!!!All prices based on yearly payment', | ||
64 | }, | ||
65 | }); | ||
66 | |||
67 | const styles = theme => ({ | ||
68 | root: { | ||
69 | background: theme.colorModalOverlayBackground, | ||
70 | width: '100%', | ||
71 | height: '100%', | ||
72 | position: 'absolute', | ||
73 | top: 0, | ||
74 | left: 0, | ||
75 | display: 'flex', | ||
76 | justifyContent: 'center', | ||
77 | alignItems: 'center', | ||
78 | zIndex: 999999, | ||
79 | overflowY: 'scroll', | ||
80 | }, | ||
81 | container: { | ||
82 | width: '80%', | ||
83 | height: 'auto', | ||
84 | background: theme.styleTypes.primary.accent, | ||
85 | padding: 40, | ||
86 | borderRadius: theme.borderRadius, | ||
87 | maxWidth: 1000, | ||
88 | |||
89 | '& h1, & h2': { | ||
90 | textAlign: 'center', | ||
91 | color: theme.styleTypes.primary.contrast, | ||
92 | }, | ||
93 | }, | ||
94 | plans: { | ||
95 | display: 'flex', | ||
96 | margin: [40, 0, 0], | ||
97 | height: 'auto', | ||
98 | |||
99 | '& > div': { | ||
100 | margin: [0, 15], | ||
101 | flex: 1, | ||
102 | height: 'auto', | ||
103 | background: theme.styleTypes.primary.contrast, | ||
104 | boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()], | ||
105 | }, | ||
106 | }, | ||
107 | bigIcon: { | ||
108 | background: theme.styleTypes.danger.accent, | ||
109 | width: 120, | ||
110 | height: 120, | ||
111 | display: 'flex', | ||
112 | alignItems: 'center', | ||
113 | borderRadius: '100%', | ||
114 | justifyContent: 'center', | ||
115 | margin: [-100, 'auto', 20], | ||
116 | |||
117 | '& svg': { | ||
118 | width: '80px !important', | ||
119 | height: '80px !important', | ||
120 | filter: 'drop-shadow( 0px 2px 3px rgba(0, 0, 0, 0.3))', | ||
121 | fill: theme.styleTypes.danger.contrast, | ||
122 | }, | ||
123 | }, | ||
124 | headline: { | ||
125 | fontSize: 40, | ||
126 | }, | ||
127 | subheadline: { | ||
128 | maxWidth: 660, | ||
129 | fontSize: 22, | ||
130 | lineHeight: 1.1, | ||
131 | margin: [0, 'auto'], | ||
132 | }, | ||
133 | featureList: { | ||
134 | '& li': { | ||
135 | borderBottom: [1, 'solid', '#CECECE'], | ||
136 | }, | ||
137 | }, | ||
138 | footer: { | ||
139 | display: 'flex', | ||
140 | color: theme.styleTypes.primary.contrast, | ||
141 | marginTop: 20, | ||
142 | padding: [0, 15], | ||
143 | }, | ||
144 | fullFeatureList: { | ||
145 | marginRight: 'auto', | ||
146 | textAlign: 'center', | ||
147 | display: 'flex', | ||
148 | justifyContent: 'center', | ||
149 | alignItems: 'center', | ||
150 | color: `${theme.styleTypes.primary.contrast} !important`, | ||
151 | |||
152 | '& svg': { | ||
153 | marginRight: 5, | ||
154 | }, | ||
155 | }, | ||
156 | scrollContainer: { | ||
157 | border: '1px solid red', | ||
158 | overflow: 'scroll-x', | ||
159 | }, | ||
160 | featuredPlan: { | ||
161 | transform: 'scale(1.05)', | ||
162 | }, | ||
163 | disclaimer: { | ||
164 | textAlign: 'right', | ||
165 | margin: [10, 15, 0, 0], | ||
166 | }, | ||
167 | }); | ||
168 | |||
169 | @injectSheet(styles) @observer | ||
170 | class PlanSelection extends Component { | ||
171 | static propTypes = { | ||
172 | classes: PropTypes.object.isRequired, | ||
173 | firstname: PropTypes.string.isRequired, | ||
174 | plans: PropTypes.object.isRequired, | ||
175 | currency: PropTypes.string.isRequired, | ||
176 | subscriptionExpired: PropTypes.bool.isRequired, | ||
177 | upgradeAccount: PropTypes.func.isRequired, | ||
178 | stayOnFree: PropTypes.func.isRequired, | ||
179 | hadSubscription: PropTypes.bool.isRequired, | ||
180 | }; | ||
181 | |||
182 | static contextTypes = { | ||
183 | intl: intlShape, | ||
184 | }; | ||
185 | |||
186 | componentDidMount() { | ||
187 | } | ||
188 | |||
189 | render() { | ||
190 | const { | ||
191 | classes, | ||
192 | firstname, | ||
193 | plans, | ||
194 | currency, | ||
195 | subscriptionExpired, | ||
196 | upgradeAccount, | ||
197 | stayOnFree, | ||
198 | hadSubscription, | ||
199 | } = this.props; | ||
200 | |||
201 | const { intl } = this.context; | ||
202 | |||
203 | return ( | ||
204 | <Appear> | ||
205 | <div | ||
206 | className={classes.root} | ||
207 | > | ||
208 | <div className={classes.container}> | ||
209 | <div className={classes.bigIcon}> | ||
210 | <Icon icon={mdiRocket} /> | ||
211 | </div> | ||
212 | <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1> | ||
213 | <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2> | ||
214 | <div className={classes.plans}> | ||
215 | <PlanItem | ||
216 | name={i18nPlanName(PLANS.FREE, intl)} | ||
217 | text={intl.formatMessage(messages.textFree)} | ||
218 | price={0} | ||
219 | currency={currency} | ||
220 | ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)} | ||
221 | upgrade={() => stayOnFree()} | ||
222 | simpleCTA | ||
223 | > | ||
224 | <FeatureList | ||
225 | plan={PLANS.FREE} | ||
226 | className={classes.featureList} | ||
227 | /> | ||
228 | </PlanItem> | ||
229 | <PlanItem | ||
230 | name={i18nPlanName(plans.pro.yearly.id, intl)} | ||
231 | text={intl.formatMessage(messages.textProfessional)} | ||
232 | price={plans.pro.yearly.price} | ||
233 | currency={currency} | ||
234 | ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)} | ||
235 | upgrade={() => upgradeAccount(plans.pro.yearly.id)} | ||
236 | className={classes.featuredPlan} | ||
237 | perUser | ||
238 | bestValue | ||
239 | > | ||
240 | <FeatureList | ||
241 | plan={PLANS.PRO} | ||
242 | className={classes.featureList} | ||
243 | /> | ||
244 | </PlanItem> | ||
245 | <PlanItem | ||
246 | name={i18nPlanName(plans.personal.yearly.id, intl)} | ||
247 | text={intl.formatMessage(messages.textPersonal)} | ||
248 | price={plans.personal.yearly.price} | ||
249 | currency={currency} | ||
250 | ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)} | ||
251 | upgrade={() => upgradeAccount(plans.personal.yearly.id)} | ||
252 | > | ||
253 | <FeatureList | ||
254 | plan={PLANS.PERSONAL} | ||
255 | className={classes.featureList} | ||
256 | /> | ||
257 | </PlanItem> | ||
258 | </div> | ||
259 | <div className={classes.footer}> | ||
260 | <a | ||
261 | href="https://meetfranz.com/pricing" | ||
262 | target="_blank" | ||
263 | className={classes.fullFeatureList} | ||
264 | > | ||
265 | <Icon icon={mdiArrowRight} /> | ||
266 | {intl.formatMessage(messages.fullFeatureList)} | ||
267 | </a> | ||
268 | {/* <p className={classes.disclaimer}> */} | ||
269 | {intl.formatMessage(messages.pricesBasedOnAnnualPayment)} | ||
270 | {/* </p> */} | ||
271 | </div> | ||
272 | </div> | ||
273 | </div> | ||
274 | </Appear> | ||
275 | ); | ||
276 | } | ||
277 | } | ||
278 | |||
279 | export default PlanSelection; | ||
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js new file mode 100644 index 000000000..d202c924e --- /dev/null +++ b/src/features/planSelection/containers/PlanSelectionScreen.js | |||
@@ -0,0 +1,123 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import { remote } from 'electron'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | |||
7 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
8 | import UserStore from '../../../stores/UserStore'; | ||
9 | import PlanSelection from '../components/PlanSelection'; | ||
10 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | ||
11 | import { planSelectionStore } from '..'; | ||
12 | |||
13 | const { dialog, app } = remote; | ||
14 | |||
15 | const messages = defineMessages({ | ||
16 | dialogTitle: { | ||
17 | id: 'feature.planSelection.fullscreen.dialog.title', | ||
18 | defaultMessage: '!!!Downgrade your Franz Plan', | ||
19 | }, | ||
20 | dialogMessage: { | ||
21 | id: 'feature.planSelection.fullscreen.dialog.message', | ||
22 | defaultMessage: '!!!You\'re about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.', | ||
23 | }, | ||
24 | dialogCTADowngrade: { | ||
25 | id: 'feature.planSelection.fullscreen.dialog.cta.downgrade', | ||
26 | defaultMessage: '!!!Downgrade to Free', | ||
27 | }, | ||
28 | dialogCTAUpgrade: { | ||
29 | id: 'feature.planSelection.fullscreen.dialog.cta.upgrade', | ||
30 | defaultMessage: '!!!Choose Personal', | ||
31 | }, | ||
32 | }); | ||
33 | |||
34 | @inject('stores', 'actions') @observer | ||
35 | class PlanSelectionScreen extends Component { | ||
36 | static contextTypes = { | ||
37 | intl: intlShape, | ||
38 | }; | ||
39 | |||
40 | upgradeAccount(planId) { | ||
41 | const { upgradeAccount } = this.props.actions.payment; | ||
42 | |||
43 | upgradeAccount({ | ||
44 | planId, | ||
45 | }); | ||
46 | } | ||
47 | |||
48 | render() { | ||
49 | if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) { | ||
50 | return null; | ||
51 | } | ||
52 | |||
53 | const { intl } = this.context; | ||
54 | |||
55 | const { user, features } = this.props.stores; | ||
56 | const { plans, currency } = features.features.pricingConfig; | ||
57 | const { activateTrial } = this.props.actions.user; | ||
58 | const { downgradeAccount, hideOverlay } = this.props.actions.planSelection; | ||
59 | |||
60 | return ( | ||
61 | <ErrorBoundary> | ||
62 | <PlanSelection | ||
63 | firstname={user.data.firstname} | ||
64 | plans={plans} | ||
65 | currency={currency} | ||
66 | upgradeAccount={(planId) => { | ||
67 | if (user.data.hadSubscription) { | ||
68 | this.upgradeAccount(planId); | ||
69 | } else { | ||
70 | activateTrial({ | ||
71 | planId, | ||
72 | }); | ||
73 | } | ||
74 | }} | ||
75 | stayOnFree={() => { | ||
76 | const selection = dialog.showMessageBoxSync(app.mainWindow, { | ||
77 | type: 'question', | ||
78 | message: intl.formatMessage(messages.dialogTitle), | ||
79 | detail: intl.formatMessage(messages.dialogMessage, { | ||
80 | currency, | ||
81 | price: plans.personal.yearly.price, | ||
82 | }), | ||
83 | buttons: [ | ||
84 | intl.formatMessage(messages.dialogCTADowngrade), | ||
85 | intl.formatMessage(messages.dialogCTAUpgrade), | ||
86 | ], | ||
87 | }); | ||
88 | |||
89 | if (selection === 0) { | ||
90 | downgradeAccount(); | ||
91 | hideOverlay(); | ||
92 | } else { | ||
93 | this.upgradeAccount(plans.personal.yearly.id); | ||
94 | } | ||
95 | }} | ||
96 | subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded} | ||
97 | hadSubscription={user.data.hadSubscription} | ||
98 | /> | ||
99 | </ErrorBoundary> | ||
100 | ); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | export default PlanSelectionScreen; | ||
105 | |||
106 | PlanSelectionScreen.wrappedComponent.propTypes = { | ||
107 | stores: PropTypes.shape({ | ||
108 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
109 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
110 | }).isRequired, | ||
111 | actions: PropTypes.shape({ | ||
112 | payment: PropTypes.shape({ | ||
113 | upgradeAccount: PropTypes.func.isRequired, | ||
114 | }), | ||
115 | planSelection: PropTypes.shape({ | ||
116 | downgradeAccount: PropTypes.func.isRequired, | ||
117 | hideOverlay: PropTypes.func.isRequired, | ||
118 | }), | ||
119 | user: PropTypes.shape({ | ||
120 | activateTrial: PropTypes.func.isRequired, | ||
121 | }), | ||
122 | }).isRequired, | ||
123 | }; | ||
diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js new file mode 100644 index 000000000..890be8871 --- /dev/null +++ b/src/features/planSelection/index.js | |||
@@ -0,0 +1,28 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import PlanSelectionStore from './store'; | ||
3 | |||
4 | const debug = require('debug')('Franz:feature:planSelection'); | ||
5 | |||
6 | export const planSelectionStore = new PlanSelectionStore(); | ||
7 | |||
8 | export default function initPlanSelection(stores, actions) { | ||
9 | stores.planSelection = planSelectionStore; | ||
10 | const { features } = stores; | ||
11 | |||
12 | // Toggle planSelection feature | ||
13 | reaction( | ||
14 | () => features.features.isPlanSelectionEnabled, | ||
15 | (isEnabled) => { | ||
16 | if (isEnabled) { | ||
17 | debug('Initializing `planSelection` feature'); | ||
18 | planSelectionStore.start(stores, actions); | ||
19 | } else if (planSelectionStore.isFeatureActive) { | ||
20 | debug('Disabling `planSelection` feature'); | ||
21 | planSelectionStore.stop(); | ||
22 | } | ||
23 | }, | ||
24 | { | ||
25 | fireImmediately: true, | ||
26 | }, | ||
27 | ); | ||
28 | } | ||
diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js new file mode 100644 index 000000000..448e323ff --- /dev/null +++ b/src/features/planSelection/store.js | |||
@@ -0,0 +1,68 @@ | |||
1 | import { | ||
2 | action, | ||
3 | observable, | ||
4 | computed, | ||
5 | } from 'mobx'; | ||
6 | |||
7 | import { planSelectionActions } from './actions'; | ||
8 | import { FeatureStore } from '../utils/FeatureStore'; | ||
9 | import { createActionBindings } from '../utils/ActionBinding'; | ||
10 | import { downgradeUserRequest } from './api'; | ||
11 | |||
12 | const debug = require('debug')('Franz:feature:planSelection:store'); | ||
13 | |||
14 | export default class PlanSelectionStore extends FeatureStore { | ||
15 | @observable isFeatureEnabled = false; | ||
16 | |||
17 | @observable isFeatureActive = false; | ||
18 | |||
19 | @observable hideOverlay = false; | ||
20 | |||
21 | @computed get showPlanSelectionOverlay() { | ||
22 | const { team, isPremium } = this.stores.user; | ||
23 | if (team && !this.hideOverlay && !isPremium) { | ||
24 | return team.state === 'expired' && !team.userHasDowngraded; | ||
25 | } | ||
26 | |||
27 | return false; | ||
28 | } | ||
29 | |||
30 | // ========== PUBLIC API ========= // | ||
31 | |||
32 | @action start(stores, actions, api) { | ||
33 | debug('PlanSelectionStore::start'); | ||
34 | this.stores = stores; | ||
35 | this.actions = actions; | ||
36 | this.api = api; | ||
37 | |||
38 | // ACTIONS | ||
39 | |||
40 | this._registerActions(createActionBindings([ | ||
41 | [planSelectionActions.downgradeAccount, this._downgradeAccount], | ||
42 | [planSelectionActions.hideOverlay, this._hideOverlay], | ||
43 | ])); | ||
44 | |||
45 | this.isFeatureActive = true; | ||
46 | } | ||
47 | |||
48 | @action stop() { | ||
49 | super.stop(); | ||
50 | debug('PlanSelectionStore::stop'); | ||
51 | this.isFeatureActive = false; | ||
52 | } | ||
53 | |||
54 | // ========== PRIVATE METHODS ========= // | ||
55 | |||
56 | // Actions | ||
57 | @action _downgradeAccount = () => { | ||
58 | downgradeUserRequest.execute(); | ||
59 | } | ||
60 | |||
61 | @action _hideOverlay = () => { | ||
62 | this.hideOverlay = true; | ||
63 | } | ||
64 | |||
65 | @action _showOverlay = () => { | ||
66 | this.hideOverlay = false; | ||
67 | } | ||
68 | } | ||
diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js index 217e926f9..04e3684ae 100644 --- a/src/features/shareFranz/index.js +++ b/src/features/shareFranz/index.js | |||
@@ -2,6 +2,7 @@ import { observable, reaction } from 'mobx'; | |||
2 | import ms from 'ms'; | 2 | import ms from 'ms'; |
3 | 3 | ||
4 | import { state as delayAppState } from '../delayApp'; | 4 | import { state as delayAppState } from '../delayApp'; |
5 | import { planSelectionStore } from '../planSelection'; | ||
5 | 6 | ||
6 | export { default as Component } from './Component'; | 7 | export { default as Component } from './Component'; |
7 | 8 | ||
@@ -31,7 +32,7 @@ export default function initialize(stores) { | |||
31 | () => stores.user.isLoggedIn, | 32 | () => stores.user.isLoggedIn, |
32 | () => { | 33 | () => { |
33 | setTimeout(() => { | 34 | setTimeout(() => { |
34 | if (stores.settings.stats.appStarts % 50 === 0) { | 35 | if (stores.settings.stats.appStarts % 50 === 0 && !planSelectionStore.showPlanSelectionOverlay) { |
35 | if (delayAppState.isDelayAppScreenVisible) { | 36 | if (delayAppState.isDelayAppScreenVisible) { |
36 | debug('Delaying share modal by 5 minutes'); | 37 | debug('Delaying share modal by 5 minutes'); |
37 | setTimeout(() => showModal(), ms('5m')); | 38 | setTimeout(() => showModal(), ms('5m')); |
diff --git a/src/features/trialStatusBar/actions.js b/src/features/trialStatusBar/actions.js new file mode 100644 index 000000000..38df76458 --- /dev/null +++ b/src/features/trialStatusBar/actions.js | |||
@@ -0,0 +1,13 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | ||
3 | |||
4 | export const trialStatusBarActions = createActionsFromDefinitions({ | ||
5 | upgradeAccount: { | ||
6 | planId: PropTypes.string.isRequired, | ||
7 | onCloseWindow: PropTypes.func.isRequired, | ||
8 | }, | ||
9 | downgradeAccount: {}, | ||
10 | hideOverlay: {}, | ||
11 | }, PropTypes.checkPropTypes); | ||
12 | |||
13 | export default trialStatusBarActions; | ||
diff --git a/src/features/trialStatusBar/components/ProgressBar.js b/src/features/trialStatusBar/components/ProgressBar.js new file mode 100644 index 000000000..41b74d396 --- /dev/null +++ b/src/features/trialStatusBar/components/ProgressBar.js | |||
@@ -0,0 +1,45 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | |||
6 | const styles = theme => ({ | ||
7 | root: { | ||
8 | background: theme.trialStatusBar.progressBar.background, | ||
9 | width: '25%', | ||
10 | maxWidth: 200, | ||
11 | height: 8, | ||
12 | display: 'flex', | ||
13 | alignItems: 'center', | ||
14 | borderRadius: theme.borderRadius, | ||
15 | overflow: 'hidden', | ||
16 | }, | ||
17 | progress: { | ||
18 | background: theme.trialStatusBar.progressBar.progressIndicator, | ||
19 | width: ({ percent }) => `${percent}%`, | ||
20 | height: '100%', | ||
21 | }, | ||
22 | }); | ||
23 | |||
24 | @injectSheet(styles) @observer | ||
25 | class ProgressBar extends Component { | ||
26 | static propTypes = { | ||
27 | classes: PropTypes.object.isRequired, | ||
28 | }; | ||
29 | |||
30 | render() { | ||
31 | const { | ||
32 | classes, | ||
33 | } = this.props; | ||
34 | |||
35 | return ( | ||
36 | <div | ||
37 | className={classes.root} | ||
38 | > | ||
39 | <div className={classes.progress} /> | ||
40 | </div> | ||
41 | ); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | export default ProgressBar; | ||
diff --git a/src/features/trialStatusBar/components/TrialStatusBar.js b/src/features/trialStatusBar/components/TrialStatusBar.js new file mode 100644 index 000000000..b8fe4acc9 --- /dev/null +++ b/src/features/trialStatusBar/components/TrialStatusBar.js | |||
@@ -0,0 +1,135 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import { Icon } from '@meetfranz/ui'; | ||
7 | import { mdiArrowRight, mdiWindowClose } from '@mdi/js'; | ||
8 | import classnames from 'classnames'; | ||
9 | |||
10 | import ProgressBar from './ProgressBar'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | restTime: { | ||
14 | id: 'feature.trialStatusBar.restTime', | ||
15 | defaultMessage: '!!!Your Free Franz {plan} Trial ends in {time}.', | ||
16 | }, | ||
17 | expired: { | ||
18 | id: 'feature.trialStatusBar.expired', | ||
19 | defaultMessage: '!!!Your free Franz {plan} Trial has expired, please upgrade your account.', | ||
20 | }, | ||
21 | cta: { | ||
22 | id: 'feature.trialStatusBar.cta', | ||
23 | defaultMessage: '!!!Upgrade now', | ||
24 | }, | ||
25 | }); | ||
26 | |||
27 | const styles = theme => ({ | ||
28 | root: { | ||
29 | background: theme.trialStatusBar.bar.background, | ||
30 | width: '100%', | ||
31 | height: 25, | ||
32 | order: 10, | ||
33 | display: 'flex', | ||
34 | alignItems: 'center', | ||
35 | fontSize: 12, | ||
36 | padding: [0, 10], | ||
37 | justifyContent: 'flex-end', | ||
38 | }, | ||
39 | ended: { | ||
40 | background: theme.styleTypes.warning.accent, | ||
41 | color: theme.styleTypes.warning.contrast, | ||
42 | }, | ||
43 | message: { | ||
44 | marginLeft: 20, | ||
45 | }, | ||
46 | action: { | ||
47 | marginLeft: 20, | ||
48 | fontSize: 12, | ||
49 | color: theme.colorText, | ||
50 | textDecoration: 'underline', | ||
51 | display: 'flex', | ||
52 | |||
53 | '& svg': { | ||
54 | margin: [1, 2, 0, 0], | ||
55 | }, | ||
56 | }, | ||
57 | }); | ||
58 | |||
59 | @injectSheet(styles) @observer | ||
60 | class TrialStatusBar extends Component { | ||
61 | static propTypes = { | ||
62 | planName: PropTypes.string.isRequired, | ||
63 | percent: PropTypes.number.isRequired, | ||
64 | upgradeAccount: PropTypes.func.isRequired, | ||
65 | hideOverlay: PropTypes.func.isRequired, | ||
66 | trialEnd: PropTypes.string.isRequired, | ||
67 | hasEnded: PropTypes.bool.isRequired, | ||
68 | classes: PropTypes.object.isRequired, | ||
69 | }; | ||
70 | |||
71 | static contextTypes = { | ||
72 | intl: intlShape, | ||
73 | }; | ||
74 | |||
75 | render() { | ||
76 | const { | ||
77 | planName, | ||
78 | percent, | ||
79 | upgradeAccount, | ||
80 | hideOverlay, | ||
81 | trialEnd, | ||
82 | hasEnded, | ||
83 | classes, | ||
84 | } = this.props; | ||
85 | |||
86 | const { intl } = this.context; | ||
87 | |||
88 | return ( | ||
89 | <div | ||
90 | className={classnames({ | ||
91 | [classes.root]: true, | ||
92 | [classes.ended]: hasEnded, | ||
93 | })} | ||
94 | > | ||
95 | <ProgressBar | ||
96 | percent={percent} | ||
97 | /> | ||
98 | {' '} | ||
99 | <span className={classes.message}> | ||
100 | {!hasEnded ? ( | ||
101 | intl.formatMessage(messages.restTime, { | ||
102 | plan: planName, | ||
103 | time: trialEnd, | ||
104 | }) | ||
105 | ) : ( | ||
106 | intl.formatMessage(messages.expired, { | ||
107 | plan: planName, | ||
108 | }) | ||
109 | )} | ||
110 | </span> | ||
111 | <button | ||
112 | className={classes.action} | ||
113 | type="button" | ||
114 | onClick={() => { | ||
115 | upgradeAccount(); | ||
116 | }} | ||
117 | > | ||
118 | <Icon icon={mdiArrowRight} /> | ||
119 | {intl.formatMessage(messages.cta)} | ||
120 | </button> | ||
121 | <button | ||
122 | className={classes.action} | ||
123 | type="button" | ||
124 | onClick={() => { | ||
125 | hideOverlay(); | ||
126 | }} | ||
127 | > | ||
128 | <Icon icon={mdiWindowClose} /> | ||
129 | </button> | ||
130 | </div> | ||
131 | ); | ||
132 | } | ||
133 | } | ||
134 | |||
135 | export default TrialStatusBar; | ||
diff --git a/src/features/trialStatusBar/containers/TrialStatusBarScreen.js b/src/features/trialStatusBar/containers/TrialStatusBarScreen.js new file mode 100644 index 000000000..e15a1204f --- /dev/null +++ b/src/features/trialStatusBar/containers/TrialStatusBarScreen.js | |||
@@ -0,0 +1,109 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import ms from 'ms'; | ||
5 | import { intlShape } from 'react-intl'; | ||
6 | |||
7 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
8 | import UserStore from '../../../stores/UserStore'; | ||
9 | import TrialStatusBar from '../components/TrialStatusBar'; | ||
10 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | ||
11 | import { trialStatusBarStore } from '..'; | ||
12 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
13 | |||
14 | @inject('stores', 'actions') @observer | ||
15 | class TrialStatusBarScreen extends Component { | ||
16 | static contextTypes = { | ||
17 | intl: intlShape, | ||
18 | }; | ||
19 | |||
20 | state = { | ||
21 | showOverlay: true, | ||
22 | percent: 0, | ||
23 | restTime: '', | ||
24 | hasEnded: false, | ||
25 | }; | ||
26 | |||
27 | percentInterval = null; | ||
28 | |||
29 | componentDidMount() { | ||
30 | this.percentInterval = setInterval(() => { | ||
31 | this.calculateRestTime(); | ||
32 | }, ms('1m')); | ||
33 | |||
34 | this.calculateRestTime(); | ||
35 | } | ||
36 | |||
37 | componentWillUnmount() { | ||
38 | clearInterval(this.percentInterval); | ||
39 | } | ||
40 | |||
41 | calculateRestTime() { | ||
42 | const { trialEndTime } = trialStatusBarStore; | ||
43 | const percent = Math.abs(100 - Math.abs(trialEndTime.asMilliseconds()) * 100 / ms('14d')).toFixed(2); | ||
44 | const restTime = trialEndTime.humanize(); | ||
45 | const hasEnded = trialEndTime.asMilliseconds() > 0; | ||
46 | |||
47 | this.setState({ | ||
48 | percent, | ||
49 | restTime, | ||
50 | hasEnded, | ||
51 | }); | ||
52 | } | ||
53 | |||
54 | hideOverlay() { | ||
55 | this.setState({ | ||
56 | showOverlay: false, | ||
57 | }); | ||
58 | } | ||
59 | |||
60 | |||
61 | render() { | ||
62 | const { intl } = this.context; | ||
63 | |||
64 | const { | ||
65 | showOverlay, | ||
66 | percent, | ||
67 | restTime, | ||
68 | hasEnded, | ||
69 | } = this.state; | ||
70 | |||
71 | if (!trialStatusBarStore || !trialStatusBarStore.isFeatureActive || !showOverlay || !trialStatusBarStore.showTrialStatusBarOverlay) { | ||
72 | return null; | ||
73 | } | ||
74 | |||
75 | const { user } = this.props.stores; | ||
76 | const { upgradeAccount } = this.props.actions.payment; | ||
77 | |||
78 | const planName = i18nPlanName(user.team.plan, intl); | ||
79 | |||
80 | return ( | ||
81 | <ErrorBoundary> | ||
82 | <TrialStatusBar | ||
83 | planName={planName} | ||
84 | percent={parseFloat(percent < 5 ? 5 : percent)} | ||
85 | trialEnd={restTime} | ||
86 | upgradeAccount={() => upgradeAccount({ | ||
87 | planId: user.team.plan, | ||
88 | })} | ||
89 | hideOverlay={() => this.hideOverlay()} | ||
90 | hasEnded={hasEnded} | ||
91 | /> | ||
92 | </ErrorBoundary> | ||
93 | ); | ||
94 | } | ||
95 | } | ||
96 | |||
97 | export default TrialStatusBarScreen; | ||
98 | |||
99 | TrialStatusBarScreen.wrappedComponent.propTypes = { | ||
100 | stores: PropTypes.shape({ | ||
101 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
102 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
103 | }).isRequired, | ||
104 | actions: PropTypes.shape({ | ||
105 | payment: PropTypes.shape({ | ||
106 | upgradeAccount: PropTypes.func.isRequired, | ||
107 | }), | ||
108 | }).isRequired, | ||
109 | }; | ||
diff --git a/src/features/trialStatusBar/index.js b/src/features/trialStatusBar/index.js new file mode 100644 index 000000000..ec84cdfd7 --- /dev/null +++ b/src/features/trialStatusBar/index.js | |||
@@ -0,0 +1,30 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import TrialStatusBarStore from './store'; | ||
3 | |||
4 | const debug = require('debug')('Franz:feature:trialStatusBar'); | ||
5 | |||
6 | export const GA_CATEGORY_TRIAL_STATUS_BAR = 'trialStatusBar'; | ||
7 | |||
8 | export const trialStatusBarStore = new TrialStatusBarStore(); | ||
9 | |||
10 | export default function initTrialStatusBar(stores, actions) { | ||
11 | stores.trialStatusBar = trialStatusBarStore; | ||
12 | const { features } = stores; | ||
13 | |||
14 | // Toggle trialStatusBar feature | ||
15 | reaction( | ||
16 | () => features.features.isTrialStatusBarEnabled, | ||
17 | (isEnabled) => { | ||
18 | if (isEnabled) { | ||
19 | debug('Initializing `trialStatusBar` feature'); | ||
20 | trialStatusBarStore.start(stores, actions); | ||
21 | } else if (trialStatusBarStore.isFeatureActive) { | ||
22 | debug('Disabling `trialStatusBar` feature'); | ||
23 | trialStatusBarStore.stop(); | ||
24 | } | ||
25 | }, | ||
26 | { | ||
27 | fireImmediately: true, | ||
28 | }, | ||
29 | ); | ||
30 | } | ||
diff --git a/src/features/trialStatusBar/store.js b/src/features/trialStatusBar/store.js new file mode 100644 index 000000000..89cf32392 --- /dev/null +++ b/src/features/trialStatusBar/store.js | |||
@@ -0,0 +1,72 @@ | |||
1 | import { | ||
2 | action, | ||
3 | observable, | ||
4 | computed, | ||
5 | } from 'mobx'; | ||
6 | import moment from 'moment'; | ||
7 | |||
8 | import { trialStatusBarActions } from './actions'; | ||
9 | import { FeatureStore } from '../utils/FeatureStore'; | ||
10 | import { createActionBindings } from '../utils/ActionBinding'; | ||
11 | |||
12 | const debug = require('debug')('Franz:feature:trialStatusBar:store'); | ||
13 | |||
14 | export default class TrialStatusBarStore extends FeatureStore { | ||
15 | @observable isFeatureActive = false; | ||
16 | |||
17 | @observable isFeatureEnabled = false; | ||
18 | |||
19 | @computed get showTrialStatusBarOverlay() { | ||
20 | if (this.isFeatureActive) { | ||
21 | const { team } = this.stores.user; | ||
22 | if (team && !this.hideOverlay) { | ||
23 | return team.state !== 'expired' && team.isTrial; | ||
24 | } | ||
25 | } | ||
26 | |||
27 | return false; | ||
28 | } | ||
29 | |||
30 | @computed get trialEndTime() { | ||
31 | if (this.isFeatureActive) { | ||
32 | const { team } = this.stores.user; | ||
33 | |||
34 | if (team && !this.hideOverlay) { | ||
35 | return moment.duration(moment().diff(team.trialEnd)); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | return moment.duration(); | ||
40 | } | ||
41 | |||
42 | // ========== PUBLIC API ========= // | ||
43 | |||
44 | @action start(stores, actions, api) { | ||
45 | debug('TrialStatusBarStore::start'); | ||
46 | this.stores = stores; | ||
47 | this.actions = actions; | ||
48 | this.api = api; | ||
49 | |||
50 | // ACTIONS | ||
51 | |||
52 | this._registerActions(createActionBindings([ | ||
53 | [trialStatusBarActions.hideOverlay, this._hideOverlay], | ||
54 | ])); | ||
55 | |||
56 | this.isFeatureActive = true; | ||
57 | } | ||
58 | |||
59 | @action stop() { | ||
60 | super.stop(); | ||
61 | debug('TrialStatusBarStore::stop'); | ||
62 | this.isFeatureActive = false; | ||
63 | } | ||
64 | |||
65 | // ========== PRIVATE METHODS ========= // | ||
66 | |||
67 | // Actions | ||
68 | |||
69 | @action _hideOverlay = () => { | ||
70 | this.hideOverlay = true; | ||
71 | } | ||
72 | } | ||
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index e991b9909..baa94f6b3 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js | |||
@@ -7,7 +7,7 @@ import { H1, Icon, ProBadge } from '@meetfranz/ui'; | |||
7 | import { Button } from '@meetfranz/forms/lib'; | 7 | import { Button } from '@meetfranz/forms/lib'; |
8 | import ReactTooltip from 'react-tooltip'; | 8 | import ReactTooltip from 'react-tooltip'; |
9 | 9 | ||
10 | import { mdiPlusBox, mdiSettings } from '@mdi/js'; | 10 | import { mdiPlusBox, mdiSettings, mdiStar } from '@mdi/js'; |
11 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; | 11 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; |
12 | import { workspaceActions } from '../actions'; | 12 | import { workspaceActions } from '../actions'; |
13 | import { workspaceStore } from '../index'; | 13 | import { workspaceStore } from '../index'; |
@@ -51,6 +51,8 @@ const styles = theme => ({ | |||
51 | drawer: { | 51 | drawer: { |
52 | background: theme.workspaces.drawer.background, | 52 | background: theme.workspaces.drawer.background, |
53 | width: `${theme.workspaces.drawer.width}px`, | 53 | width: `${theme.workspaces.drawer.width}px`, |
54 | display: 'flex', | ||
55 | flexDirection: 'column', | ||
54 | }, | 56 | }, |
55 | headline: { | 57 | headline: { |
56 | fontSize: '24px', | 58 | fontSize: '24px', |
@@ -74,6 +76,7 @@ const styles = theme => ({ | |||
74 | }, | 76 | }, |
75 | workspaces: { | 77 | workspaces: { |
76 | height: 'auto', | 78 | height: 'auto', |
79 | overflowY: 'scroll', | ||
77 | }, | 80 | }, |
78 | premiumAnnouncement: { | 81 | premiumAnnouncement: { |
79 | padding: '20px', | 82 | padding: '20px', |
@@ -88,7 +91,7 @@ const styles = theme => ({ | |||
88 | addNewWorkspaceLabel: { | 91 | addNewWorkspaceLabel: { |
89 | height: 'auto', | 92 | height: 'auto', |
90 | color: theme.workspaces.drawer.buttons.color, | 93 | color: theme.workspaces.drawer.buttons.color, |
91 | marginTop: 40, | 94 | margin: [40, 0], |
92 | textAlign: 'center', | 95 | textAlign: 'center', |
93 | '& > svg': { | 96 | '& > svg': { |
94 | fill: theme.workspaces.drawer.buttons.color, | 97 | fill: theme.workspaces.drawer.buttons.color, |
@@ -172,7 +175,7 @@ class WorkspaceDrawer extends Component { | |||
172 | className={classes.premiumCtaButton} | 175 | className={classes.premiumCtaButton} |
173 | buttonType="primary" | 176 | buttonType="primary" |
174 | label={intl.formatMessage(messages.reactivatePremiumAccount)} | 177 | label={intl.formatMessage(messages.reactivatePremiumAccount)} |
175 | icon="mdiStar" | 178 | icon={mdiStar} |
176 | onClick={() => { | 179 | onClick={() => { |
177 | onUpgradeAccountClick(); | 180 | onUpgradeAccountClick(); |
178 | }} | 181 | }} |
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index 977b23999..b499e02a4 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js | |||
@@ -5,6 +5,7 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
5 | import injectSheet from 'react-jss'; | 5 | import injectSheet from 'react-jss'; |
6 | import { Infobox, Badge } from '@meetfranz/ui'; | 6 | import { Infobox, Badge } from '@meetfranz/ui'; |
7 | 7 | ||
8 | import { mdiCheckboxMarkedCircleOutline } from '@mdi/js'; | ||
8 | import Loader from '../../../components/ui/Loader'; | 9 | import Loader from '../../../components/ui/Loader'; |
9 | import WorkspaceItem from './WorkspaceItem'; | 10 | import WorkspaceItem from './WorkspaceItem'; |
10 | import CreateWorkspaceForm from './CreateWorkspaceForm'; | 11 | import CreateWorkspaceForm from './CreateWorkspaceForm'; |
@@ -128,7 +129,7 @@ class WorkspacesDashboard extends Component { | |||
128 | <Appear className={classes.appear}> | 129 | <Appear className={classes.appear}> |
129 | <Infobox | 130 | <Infobox |
130 | type="success" | 131 | type="success" |
131 | icon="mdiCheckboxMarkedCircleOutline" | 132 | icon={mdiCheckboxMarkedCircleOutline} |
132 | dismissable | 133 | dismissable |
133 | onUnmount={updateWorkspaceRequest.reset} | 134 | onUnmount={updateWorkspaceRequest.reset} |
134 | > | 135 | > |
@@ -142,7 +143,7 @@ class WorkspacesDashboard extends Component { | |||
142 | <Appear className={classes.appear}> | 143 | <Appear className={classes.appear}> |
143 | <Infobox | 144 | <Infobox |
144 | type="success" | 145 | type="success" |
145 | icon="mdiCheckboxMarkedCircleOutline" | 146 | icon={mdiCheckboxMarkedCircleOutline} |
146 | dismissable | 147 | dismissable |
147 | onUnmount={deleteWorkspaceRequest.reset} | 148 | onUnmount={deleteWorkspaceRequest.reset} |
148 | > | 149 | > |
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 949f8a792..5c90ff180 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js | |||
@@ -47,6 +47,11 @@ export default class WorkspacesStore extends FeatureStore { | |||
47 | return getUserWorkspacesRequest.result || []; | 47 | return getUserWorkspacesRequest.result || []; |
48 | } | 48 | } |
49 | 49 | ||
50 | @computed get isLoadingWorkspaces() { | ||
51 | if (!this.isFeatureActive) return false; | ||
52 | return getUserWorkspacesRequest.isExecutingFirstTime; | ||
53 | } | ||
54 | |||
50 | @computed get settings() { | 55 | @computed get settings() { |
51 | return localStorage.getItem('workspaces') || {}; | 56 | return localStorage.getItem('workspaces') || {}; |
52 | } | 57 | } |
diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js index e0f1fd89a..ee22e4471 100644 --- a/src/helpers/plan-helpers.js +++ b/src/helpers/plan-helpers.js | |||
@@ -4,19 +4,19 @@ import { PLANS_MAPPING, PLANS } from '../config'; | |||
4 | const messages = defineMessages({ | 4 | const messages = defineMessages({ |
5 | [PLANS.PRO]: { | 5 | [PLANS.PRO]: { |
6 | id: 'pricing.plan.pro', | 6 | id: 'pricing.plan.pro', |
7 | defaultMessage: '!!!Franz Professional', | 7 | defaultMessage: '!!!Professional', |
8 | }, | 8 | }, |
9 | [PLANS.PERSONAL]: { | 9 | [PLANS.PERSONAL]: { |
10 | id: 'pricing.plan.personal', | 10 | id: 'pricing.plan.personal', |
11 | defaultMessage: '!!!Franz Personal', | 11 | defaultMessage: '!!!Personal', |
12 | }, | 12 | }, |
13 | [PLANS.FREE]: { | 13 | [PLANS.FREE]: { |
14 | id: 'pricing.plan.free', | 14 | id: 'pricing.plan.free', |
15 | defaultMessage: '!!!Franz Free', | 15 | defaultMessage: '!!!Free', |
16 | }, | 16 | }, |
17 | [PLANS.LEGACY]: { | 17 | [PLANS.LEGACY]: { |
18 | id: 'pricing.plan.legacy', | 18 | id: 'pricing.plan.legacy', |
19 | defaultMessage: '!!!Franz Premium', | 19 | defaultMessage: '!!!Premium', |
20 | }, | 20 | }, |
21 | }); | 21 | }); |
22 | 22 | ||
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index b056f0d1b..bb65ccdf2 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -539,120 +539,172 @@ | |||
539 | { | 539 | { |
540 | "descriptors": [ | 540 | "descriptors": [ |
541 | { | 541 | { |
542 | "defaultMessage": "!!!Franz Professional", | 542 | "defaultMessage": "!!!Hi {name}, welcome to Franz", |
543 | "end": { | 543 | "end": { |
544 | "column": 3, | 544 | "column": 3, |
545 | "line": 18 | 545 | "line": 18 |
546 | }, | 546 | }, |
547 | "file": "src/components/auth/Pricing.js", | 547 | "file": "src/components/auth/Pricing.js", |
548 | "id": "pricing.trial.headline", | 548 | "id": "pricing.trial.headline.pro", |
549 | "start": { | 549 | "start": { |
550 | "column": 12, | 550 | "column": 12, |
551 | "line": 15 | 551 | "line": 15 |
552 | } | 552 | } |
553 | }, | 553 | }, |
554 | { | 554 | { |
555 | "defaultMessage": "!!!Your personal welcome offer:", | 555 | "defaultMessage": "!!!We have a special treat for you.", |
556 | "end": { | 556 | "end": { |
557 | "column": 3, | 557 | "column": 3, |
558 | "line": 22 | 558 | "line": 22 |
559 | }, | 559 | }, |
560 | "file": "src/components/auth/Pricing.js", | 560 | "file": "src/components/auth/Pricing.js", |
561 | "id": "pricing.trial.subheadline", | 561 | "id": "pricing.trial.intro.specialTreat", |
562 | "start": { | 562 | "start": { |
563 | "column": 17, | 563 | "column": 16, |
564 | "line": 19 | 564 | "line": 19 |
565 | } | 565 | } |
566 | }, | 566 | }, |
567 | { | 567 | { |
568 | "defaultMessage": "!!!No strings attached", | 568 | "defaultMessage": "!!!Enjoy the full Franz Professional experience completely free for 14 days.", |
569 | "end": { | 569 | "end": { |
570 | "column": 3, | 570 | "column": 3, |
571 | "line": 26 | 571 | "line": 26 |
572 | }, | 572 | }, |
573 | "file": "src/components/auth/Pricing.js", | 573 | "file": "src/components/auth/Pricing.js", |
574 | "id": "pricing.trial.intro.tryPro", | ||
575 | "start": { | ||
576 | "column": 10, | ||
577 | "line": 23 | ||
578 | } | ||
579 | }, | ||
580 | { | ||
581 | "defaultMessage": "!!!Happy messaging,", | ||
582 | "end": { | ||
583 | "column": 3, | ||
584 | "line": 30 | ||
585 | }, | ||
586 | "file": "src/components/auth/Pricing.js", | ||
587 | "id": "pricing.trial.intro.happyMessaging", | ||
588 | "start": { | ||
589 | "column": 18, | ||
590 | "line": 27 | ||
591 | } | ||
592 | }, | ||
593 | { | ||
594 | "defaultMessage": "!!!No strings attached", | ||
595 | "end": { | ||
596 | "column": 3, | ||
597 | "line": 34 | ||
598 | }, | ||
599 | "file": "src/components/auth/Pricing.js", | ||
574 | "id": "pricing.trial.terms.headline", | 600 | "id": "pricing.trial.terms.headline", |
575 | "start": { | 601 | "start": { |
576 | "column": 29, | 602 | "column": 29, |
577 | "line": 23 | 603 | "line": 31 |
578 | } | 604 | } |
579 | }, | 605 | }, |
580 | { | 606 | { |
581 | "defaultMessage": "!!!No credit card required", | 607 | "defaultMessage": "!!!No credit card required", |
582 | "end": { | 608 | "end": { |
583 | "column": 3, | 609 | "column": 3, |
584 | "line": 30 | 610 | "line": 38 |
585 | }, | 611 | }, |
586 | "file": "src/components/auth/Pricing.js", | 612 | "file": "src/components/auth/Pricing.js", |
587 | "id": "pricing.trial.terms.noCreditCard", | 613 | "id": "pricing.trial.terms.noCreditCard", |
588 | "start": { | 614 | "start": { |
589 | "column": 16, | 615 | "column": 16, |
590 | "line": 27 | 616 | "line": 35 |
591 | } | 617 | } |
592 | }, | 618 | }, |
593 | { | 619 | { |
594 | "defaultMessage": "!!!Your free trial ends automatically after 14 days", | 620 | "defaultMessage": "!!!Your free trial ends automatically after 14 days", |
595 | "end": { | 621 | "end": { |
596 | "column": 3, | 622 | "column": 3, |
597 | "line": 34 | 623 | "line": 42 |
598 | }, | 624 | }, |
599 | "file": "src/components/auth/Pricing.js", | 625 | "file": "src/components/auth/Pricing.js", |
600 | "id": "pricing.trial.terms.automaticTrialEnd", | 626 | "id": "pricing.trial.terms.automaticTrialEnd", |
601 | "start": { | 627 | "start": { |
602 | "column": 21, | 628 | "column": 21, |
603 | "line": 31 | 629 | "line": 39 |
630 | } | ||
631 | }, | ||
632 | { | ||
633 | "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", | ||
634 | "end": { | ||
635 | "column": 3, | ||
636 | "line": 46 | ||
637 | }, | ||
638 | "file": "src/components/auth/Pricing.js", | ||
639 | "id": "pricing.trial.terms.trialWorth", | ||
640 | "start": { | ||
641 | "column": 14, | ||
642 | "line": 43 | ||
604 | } | 643 | } |
605 | }, | 644 | }, |
606 | { | 645 | { |
607 | "defaultMessage": "!!!Sorry, we could not activate your trial!", | 646 | "defaultMessage": "!!!Sorry, we could not activate your trial!", |
608 | "end": { | 647 | "end": { |
609 | "column": 3, | 648 | "column": 3, |
610 | "line": 38 | 649 | "line": 50 |
611 | }, | 650 | }, |
612 | "file": "src/components/auth/Pricing.js", | 651 | "file": "src/components/auth/Pricing.js", |
613 | "id": "pricing.trial.error", | 652 | "id": "pricing.trial.error", |
614 | "start": { | 653 | "start": { |
615 | "column": 19, | 654 | "column": 19, |
616 | "line": 35 | 655 | "line": 47 |
617 | } | 656 | } |
618 | }, | 657 | }, |
619 | { | 658 | { |
620 | "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", | 659 | "defaultMessage": "!!!Start my 14-day Franz Professional Trial", |
621 | "end": { | 660 | "end": { |
622 | "column": 3, | 661 | "column": 3, |
623 | "line": 42 | 662 | "line": 54 |
624 | }, | 663 | }, |
625 | "file": "src/components/auth/Pricing.js", | 664 | "file": "src/components/auth/Pricing.js", |
626 | "id": "pricing.trial.cta.accept", | 665 | "id": "pricing.trial.cta.accept", |
627 | "start": { | 666 | "start": { |
628 | "column": 13, | 667 | "column": 13, |
629 | "line": 39 | 668 | "line": 51 |
669 | } | ||
670 | }, | ||
671 | { | ||
672 | "defaultMessage": "!!!Start using Franz", | ||
673 | "end": { | ||
674 | "column": 3, | ||
675 | "line": 58 | ||
676 | }, | ||
677 | "file": "src/components/auth/Pricing.js", | ||
678 | "id": "pricing.trial.cta.start", | ||
679 | "start": { | ||
680 | "column": 12, | ||
681 | "line": 55 | ||
630 | } | 682 | } |
631 | }, | 683 | }, |
632 | { | 684 | { |
633 | "defaultMessage": "!!!Continue to Ferdi", | 685 | "defaultMessage": "!!!Continue to Ferdi", |
634 | "end": { | 686 | "end": { |
635 | "column": 3, | 687 | "column": 3, |
636 | "line": 46 | 688 | "line": 62 |
637 | }, | 689 | }, |
638 | "file": "src/components/auth/Pricing.js", | 690 | "file": "src/components/auth/Pricing.js", |
639 | "id": "pricing.trial.cta.skip", | 691 | "id": "pricing.trial.cta.skip", |
640 | "start": { | 692 | "start": { |
641 | "column": 11, | 693 | "column": 11, |
642 | "line": 43 | 694 | "line": 59 |
643 | } | 695 | } |
644 | }, | 696 | }, |
645 | { | 697 | { |
646 | "defaultMessage": "!!!Franz Professional includes:", | 698 | "defaultMessage": "!!!Franz Professional includes:", |
647 | "end": { | 699 | "end": { |
648 | "column": 3, | 700 | "column": 3, |
649 | "line": 50 | 701 | "line": 66 |
650 | }, | 702 | }, |
651 | "file": "src/components/auth/Pricing.js", | 703 | "file": "src/components/auth/Pricing.js", |
652 | "id": "pricing.trial.features.headline", | 704 | "id": "pricing.trial.features.headline", |
653 | "start": { | 705 | "start": { |
654 | "column": 20, | 706 | "column": 20, |
655 | "line": 47 | 707 | "line": 63 |
656 | } | 708 | } |
657 | } | 709 | } |
658 | ], | 710 | ], |
@@ -882,52 +934,52 @@ | |||
882 | "defaultMessage": "!!!Your services have been updated.", | 934 | "defaultMessage": "!!!Your services have been updated.", |
883 | "end": { | 935 | "end": { |
884 | "column": 3, | 936 | "column": 3, |
885 | "line": 31 | 937 | "line": 33 |
886 | }, | 938 | }, |
887 | "file": "src/components/layout/AppLayout.js", | 939 | "file": "src/components/layout/AppLayout.js", |
888 | "id": "infobar.servicesUpdated", | 940 | "id": "infobar.servicesUpdated", |
889 | "start": { | 941 | "start": { |
890 | "column": 19, | 942 | "column": 19, |
891 | "line": 28 | 943 | "line": 30 |
892 | } | 944 | } |
893 | }, | 945 | }, |
894 | { | 946 | { |
895 | "defaultMessage": "!!!Reload services", | 947 | "defaultMessage": "!!!Reload services", |
896 | "end": { | 948 | "end": { |
897 | "column": 3, | 949 | "column": 3, |
898 | "line": 35 | 950 | "line": 37 |
899 | }, | 951 | }, |
900 | "file": "src/components/layout/AppLayout.js", | 952 | "file": "src/components/layout/AppLayout.js", |
901 | "id": "infobar.buttonReloadServices", | 953 | "id": "infobar.buttonReloadServices", |
902 | "start": { | 954 | "start": { |
903 | "column": 24, | 955 | "column": 24, |
904 | "line": 32 | 956 | "line": 34 |
905 | } | 957 | } |
906 | }, | 958 | }, |
907 | { | 959 | { |
908 | "defaultMessage": "!!!Could not load services and user information", | 960 | "defaultMessage": "!!!Could not load services and user information", |
909 | "end": { | 961 | "end": { |
910 | "column": 3, | 962 | "column": 3, |
911 | "line": 39 | 963 | "line": 41 |
912 | }, | 964 | }, |
913 | "file": "src/components/layout/AppLayout.js", | 965 | "file": "src/components/layout/AppLayout.js", |
914 | "id": "infobar.requiredRequestsFailed", | 966 | "id": "infobar.requiredRequestsFailed", |
915 | "start": { | 967 | "start": { |
916 | "column": 26, | 968 | "column": 26, |
917 | "line": 36 | 969 | "line": 38 |
918 | } | 970 | } |
919 | }, | 971 | }, |
920 | { | 972 | { |
921 | "defaultMessage": "!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.", | 973 | "defaultMessage": "!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.", |
922 | "end": { | 974 | "end": { |
923 | "column": 3, | 975 | "column": 3, |
924 | "line": 43 | 976 | "line": 45 |
925 | }, | 977 | }, |
926 | "file": "src/components/layout/AppLayout.js", | 978 | "file": "src/components/layout/AppLayout.js", |
927 | "id": "infobar.authRequestFailed", | 979 | "id": "infobar.authRequestFailed", |
928 | "start": { | 980 | "start": { |
929 | "column": 21, | 981 | "column": 21, |
930 | "line": 40 | 982 | "line": 42 |
931 | } | 983 | } |
932 | } | 984 | } |
933 | ], | 985 | ], |
@@ -3495,133 +3547,198 @@ | |||
3495 | { | 3547 | { |
3496 | "descriptors": [ | 3548 | "descriptors": [ |
3497 | { | 3549 | { |
3550 | "defaultMessage": "!!!Choose from more than 70 Services", | ||
3551 | "end": { | ||
3552 | "column": 3, | ||
3553 | "line": 12 | ||
3554 | }, | ||
3555 | "file": "src/components/ui/FeatureList.js", | ||
3556 | "id": "pricing.features.recipes", | ||
3557 | "start": { | ||
3558 | "column": 20, | ||
3559 | "line": 9 | ||
3560 | } | ||
3561 | }, | ||
3562 | { | ||
3563 | "defaultMessage": "!!!Account Synchronisation", | ||
3564 | "end": { | ||
3565 | "column": 3, | ||
3566 | "line": 16 | ||
3567 | }, | ||
3568 | "file": "src/components/ui/FeatureList.js", | ||
3569 | "id": "pricing.features.accountSync", | ||
3570 | "start": { | ||
3571 | "column": 15, | ||
3572 | "line": 13 | ||
3573 | } | ||
3574 | }, | ||
3575 | { | ||
3576 | "defaultMessage": "!!!Desktop Notifications", | ||
3577 | "end": { | ||
3578 | "column": 3, | ||
3579 | "line": 20 | ||
3580 | }, | ||
3581 | "file": "src/components/ui/FeatureList.js", | ||
3582 | "id": "pricing.features.desktopNotifications", | ||
3583 | "start": { | ||
3584 | "column": 24, | ||
3585 | "line": 17 | ||
3586 | } | ||
3587 | }, | ||
3588 | { | ||
3498 | "defaultMessage": "!!!Add unlimited services", | 3589 | "defaultMessage": "!!!Add unlimited services", |
3499 | "end": { | 3590 | "end": { |
3500 | "column": 3, | 3591 | "column": 3, |
3501 | "line": 11 | 3592 | "line": 24 |
3502 | }, | 3593 | }, |
3503 | "file": "src/components/ui/FeatureList.js", | 3594 | "file": "src/components/ui/FeatureList.js", |
3504 | "id": "pricing.features.unlimitedServices", | 3595 | "id": "pricing.features.unlimitedServices", |
3505 | "start": { | 3596 | "start": { |
3506 | "column": 21, | 3597 | "column": 21, |
3507 | "line": 8 | 3598 | "line": 21 |
3599 | } | ||
3600 | }, | ||
3601 | { | ||
3602 | "defaultMessage": "!!!Add up to 3 services", | ||
3603 | "end": { | ||
3604 | "column": 3, | ||
3605 | "line": 28 | ||
3606 | }, | ||
3607 | "file": "src/components/ui/FeatureList.js", | ||
3608 | "id": "pricing.features.upToThreeServices", | ||
3609 | "start": { | ||
3610 | "column": 21, | ||
3611 | "line": 25 | ||
3612 | } | ||
3613 | }, | ||
3614 | { | ||
3615 | "defaultMessage": "!!!Add up to 6 services", | ||
3616 | "end": { | ||
3617 | "column": 3, | ||
3618 | "line": 32 | ||
3619 | }, | ||
3620 | "file": "src/components/ui/FeatureList.js", | ||
3621 | "id": "pricing.features.upToSixServices", | ||
3622 | "start": { | ||
3623 | "column": 19, | ||
3624 | "line": 29 | ||
3508 | } | 3625 | } |
3509 | }, | 3626 | }, |
3510 | { | 3627 | { |
3511 | "defaultMessage": "!!!Spellchecker support", | 3628 | "defaultMessage": "!!!Spellchecker support", |
3512 | "end": { | 3629 | "end": { |
3513 | "column": 3, | 3630 | "column": 3, |
3514 | "line": 15 | 3631 | "line": 36 |
3515 | }, | 3632 | }, |
3516 | "file": "src/components/ui/FeatureList.js", | 3633 | "file": "src/components/ui/FeatureList.js", |
3517 | "id": "pricing.features.spellchecker", | 3634 | "id": "pricing.features.spellchecker", |
3518 | "start": { | 3635 | "start": { |
3519 | "column": 16, | 3636 | "column": 16, |
3520 | "line": 12 | 3637 | "line": 33 |
3521 | } | 3638 | } |
3522 | }, | 3639 | }, |
3523 | { | 3640 | { |
3524 | "defaultMessage": "!!!Workspaces", | 3641 | "defaultMessage": "!!!Workspaces", |
3525 | "end": { | 3642 | "end": { |
3526 | "column": 3, | 3643 | "column": 3, |
3527 | "line": 19 | 3644 | "line": 40 |
3528 | }, | 3645 | }, |
3529 | "file": "src/components/ui/FeatureList.js", | 3646 | "file": "src/components/ui/FeatureList.js", |
3530 | "id": "pricing.features.workspaces", | 3647 | "id": "pricing.features.workspaces", |
3531 | "start": { | 3648 | "start": { |
3532 | "column": 14, | 3649 | "column": 14, |
3533 | "line": 16 | 3650 | "line": 37 |
3534 | } | 3651 | } |
3535 | }, | 3652 | }, |
3536 | { | 3653 | { |
3537 | "defaultMessage": "!!!Add Custom Websites", | 3654 | "defaultMessage": "!!!Add Custom Websites", |
3538 | "end": { | 3655 | "end": { |
3539 | "column": 3, | 3656 | "column": 3, |
3540 | "line": 23 | 3657 | "line": 44 |
3541 | }, | 3658 | }, |
3542 | "file": "src/components/ui/FeatureList.js", | 3659 | "file": "src/components/ui/FeatureList.js", |
3543 | "id": "pricing.features.customWebsites", | 3660 | "id": "pricing.features.customWebsites", |
3544 | "start": { | 3661 | "start": { |
3545 | "column": 18, | 3662 | "column": 18, |
3546 | "line": 20 | 3663 | "line": 41 |
3547 | } | 3664 | } |
3548 | }, | 3665 | }, |
3549 | { | 3666 | { |
3550 | "defaultMessage": "!!!On-premise & other Hosted Services", | 3667 | "defaultMessage": "!!!On-premise & other Hosted Services", |
3551 | "end": { | 3668 | "end": { |
3552 | "column": 3, | 3669 | "column": 3, |
3553 | "line": 27 | 3670 | "line": 48 |
3554 | }, | 3671 | }, |
3555 | "file": "src/components/ui/FeatureList.js", | 3672 | "file": "src/components/ui/FeatureList.js", |
3556 | "id": "pricing.features.onPremise", | 3673 | "id": "pricing.features.onPremise", |
3557 | "start": { | 3674 | "start": { |
3558 | "column": 13, | 3675 | "column": 13, |
3559 | "line": 24 | 3676 | "line": 45 |
3560 | } | 3677 | } |
3561 | }, | 3678 | }, |
3562 | { | 3679 | { |
3563 | "defaultMessage": "!!!Install 3rd party services", | 3680 | "defaultMessage": "!!!Install 3rd party services", |
3564 | "end": { | 3681 | "end": { |
3565 | "column": 3, | 3682 | "column": 3, |
3566 | "line": 31 | 3683 | "line": 52 |
3567 | }, | 3684 | }, |
3568 | "file": "src/components/ui/FeatureList.js", | 3685 | "file": "src/components/ui/FeatureList.js", |
3569 | "id": "pricing.features.thirdPartyServices", | 3686 | "id": "pricing.features.thirdPartyServices", |
3570 | "start": { | 3687 | "start": { |
3571 | "column": 22, | 3688 | "column": 22, |
3572 | "line": 28 | 3689 | "line": 49 |
3573 | } | 3690 | } |
3574 | }, | 3691 | }, |
3575 | { | 3692 | { |
3576 | "defaultMessage": "!!!Service Proxies", | 3693 | "defaultMessage": "!!!Service Proxies", |
3577 | "end": { | 3694 | "end": { |
3578 | "column": 3, | 3695 | "column": 3, |
3579 | "line": 35 | 3696 | "line": 56 |
3580 | }, | 3697 | }, |
3581 | "file": "src/components/ui/FeatureList.js", | 3698 | "file": "src/components/ui/FeatureList.js", |
3582 | "id": "pricing.features.serviceProxies", | 3699 | "id": "pricing.features.serviceProxies", |
3583 | "start": { | 3700 | "start": { |
3584 | "column": 18, | 3701 | "column": 18, |
3585 | "line": 32 | 3702 | "line": 53 |
3586 | } | 3703 | } |
3587 | }, | 3704 | }, |
3588 | { | 3705 | { |
3589 | "defaultMessage": "!!!Team Management", | 3706 | "defaultMessage": "!!!Team Management", |
3590 | "end": { | 3707 | "end": { |
3591 | "column": 3, | 3708 | "column": 3, |
3592 | "line": 39 | 3709 | "line": 60 |
3593 | }, | 3710 | }, |
3594 | "file": "src/components/ui/FeatureList.js", | 3711 | "file": "src/components/ui/FeatureList.js", |
3595 | "id": "pricing.features.teamManagement", | 3712 | "id": "pricing.features.teamManagement", |
3596 | "start": { | 3713 | "start": { |
3597 | "column": 18, | 3714 | "column": 18, |
3598 | "line": 36 | 3715 | "line": 57 |
3599 | } | 3716 | } |
3600 | }, | 3717 | }, |
3601 | { | 3718 | { |
3602 | "defaultMessage": "!!!No Waiting Screens", | 3719 | "defaultMessage": "!!!No Waiting Screens", |
3603 | "end": { | 3720 | "end": { |
3604 | "column": 3, | 3721 | "column": 3, |
3605 | "line": 43 | 3722 | "line": 64 |
3606 | }, | 3723 | }, |
3607 | "file": "src/components/ui/FeatureList.js", | 3724 | "file": "src/components/ui/FeatureList.js", |
3608 | "id": "pricing.features.appDelays", | 3725 | "id": "pricing.features.appDelays", |
3609 | "start": { | 3726 | "start": { |
3610 | "column": 13, | 3727 | "column": 13, |
3611 | "line": 40 | 3728 | "line": 61 |
3612 | } | 3729 | } |
3613 | }, | 3730 | }, |
3614 | { | 3731 | { |
3615 | "defaultMessage": "!!!Forever ad-free", | 3732 | "defaultMessage": "!!!Forever ad-free", |
3616 | "end": { | 3733 | "end": { |
3617 | "column": 3, | 3734 | "column": 3, |
3618 | "line": 47 | 3735 | "line": 68 |
3619 | }, | 3736 | }, |
3620 | "file": "src/components/ui/FeatureList.js", | 3737 | "file": "src/components/ui/FeatureList.js", |
3621 | "id": "pricing.features.adFree", | 3738 | "id": "pricing.features.adFree", |
3622 | "start": { | 3739 | "start": { |
3623 | "column": 10, | 3740 | "column": 10, |
3624 | "line": 44 | 3741 | "line": 65 |
3625 | } | 3742 | } |
3626 | } | 3743 | } |
3627 | ], | 3744 | ], |
@@ -4450,7 +4567,7 @@ | |||
4450 | } | 4567 | } |
4451 | }, | 4568 | }, |
4452 | { | 4569 | { |
4453 | "defaultMessage": "!!!Get a Franz Supporter License", | 4570 | "defaultMessage": "!!!Upgrade Franz", |
4454 | "end": { | 4571 | "end": { |
4455 | "column": 3, | 4572 | "column": 3, |
4456 | "line": 25 | 4573 | "line": 25 |
@@ -4494,6 +4611,299 @@ | |||
4494 | { | 4611 | { |
4495 | "descriptors": [ | 4612 | "descriptors": [ |
4496 | { | 4613 | { |
4614 | "defaultMessage": "!!!per month", | ||
4615 | "end": { | ||
4616 | "column": 3, | ||
4617 | "line": 18 | ||
4618 | }, | ||
4619 | "file": "src/features/planSelection/components/PlanItem.js", | ||
4620 | "id": "subscription.interval.perMonth", | ||
4621 | "start": { | ||
4622 | "column": 12, | ||
4623 | "line": 15 | ||
4624 | } | ||
4625 | }, | ||
4626 | { | ||
4627 | "defaultMessage": "!!!per month & user", | ||
4628 | "end": { | ||
4629 | "column": 3, | ||
4630 | "line": 22 | ||
4631 | }, | ||
4632 | "file": "src/features/planSelection/components/PlanItem.js", | ||
4633 | "id": "subscription.interval.perMonthPerUser", | ||
4634 | "start": { | ||
4635 | "column": 19, | ||
4636 | "line": 19 | ||
4637 | } | ||
4638 | }, | ||
4639 | { | ||
4640 | "defaultMessage": "!!!Best value", | ||
4641 | "end": { | ||
4642 | "column": 3, | ||
4643 | "line": 26 | ||
4644 | }, | ||
4645 | "file": "src/features/planSelection/components/PlanItem.js", | ||
4646 | "id": "subscription.bestValue", | ||
4647 | "start": { | ||
4648 | "column": 13, | ||
4649 | "line": 23 | ||
4650 | } | ||
4651 | } | ||
4652 | ], | ||
4653 | "path": "src/features/planSelection/components/PlanItem.json" | ||
4654 | }, | ||
4655 | { | ||
4656 | "descriptors": [ | ||
4657 | { | ||
4658 | "defaultMessage": "!!!Are you ready to choose, {name}", | ||
4659 | "end": { | ||
4660 | "column": 3, | ||
4661 | "line": 20 | ||
4662 | }, | ||
4663 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4664 | "id": "feature.planSelection.fullscreen.welcome", | ||
4665 | "start": { | ||
4666 | "column": 11, | ||
4667 | "line": 17 | ||
4668 | } | ||
4669 | }, | ||
4670 | { | ||
4671 | "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", | ||
4672 | "end": { | ||
4673 | "column": 3, | ||
4674 | "line": 24 | ||
4675 | }, | ||
4676 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4677 | "id": "feature.planSelection.fullscreen.subheadline", | ||
4678 | "start": { | ||
4679 | "column": 15, | ||
4680 | "line": 21 | ||
4681 | } | ||
4682 | }, | ||
4683 | { | ||
4684 | "defaultMessage": "!!!Basic functionality", | ||
4685 | "end": { | ||
4686 | "column": 3, | ||
4687 | "line": 28 | ||
4688 | }, | ||
4689 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4690 | "id": "feature.planSelection.free.text", | ||
4691 | "start": { | ||
4692 | "column": 12, | ||
4693 | "line": 25 | ||
4694 | } | ||
4695 | }, | ||
4696 | { | ||
4697 | "defaultMessage": "!!!More services, no waiting - ideal for personal use.", | ||
4698 | "end": { | ||
4699 | "column": 3, | ||
4700 | "line": 32 | ||
4701 | }, | ||
4702 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4703 | "id": "feature.planSelection.personal.text", | ||
4704 | "start": { | ||
4705 | "column": 16, | ||
4706 | "line": 29 | ||
4707 | } | ||
4708 | }, | ||
4709 | { | ||
4710 | "defaultMessage": "!!!Unlimited services and professional features for you - and your team.", | ||
4711 | "end": { | ||
4712 | "column": 3, | ||
4713 | "line": 36 | ||
4714 | }, | ||
4715 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4716 | "id": "feature.planSelection.pro.text", | ||
4717 | "start": { | ||
4718 | "column": 20, | ||
4719 | "line": 33 | ||
4720 | } | ||
4721 | }, | ||
4722 | { | ||
4723 | "defaultMessage": "!!!Stay on Free", | ||
4724 | "end": { | ||
4725 | "column": 3, | ||
4726 | "line": 40 | ||
4727 | }, | ||
4728 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4729 | "id": "feature.planSelection.cta.stayOnFree", | ||
4730 | "start": { | ||
4731 | "column": 17, | ||
4732 | "line": 37 | ||
4733 | } | ||
4734 | }, | ||
4735 | { | ||
4736 | "defaultMessage": "!!!Downgrade to Free", | ||
4737 | "end": { | ||
4738 | "column": 3, | ||
4739 | "line": 44 | ||
4740 | }, | ||
4741 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4742 | "id": "feature.planSelection.cta.ctaDowngradeFree", | ||
4743 | "start": { | ||
4744 | "column": 20, | ||
4745 | "line": 41 | ||
4746 | } | ||
4747 | }, | ||
4748 | { | ||
4749 | "defaultMessage": "!!!Start my free 14-days Trial", | ||
4750 | "end": { | ||
4751 | "column": 3, | ||
4752 | "line": 48 | ||
4753 | }, | ||
4754 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4755 | "id": "feature.planSelection.cta.trial", | ||
4756 | "start": { | ||
4757 | "column": 15, | ||
4758 | "line": 45 | ||
4759 | } | ||
4760 | }, | ||
4761 | { | ||
4762 | "defaultMessage": "!!!Choose Personal", | ||
4763 | "end": { | ||
4764 | "column": 3, | ||
4765 | "line": 52 | ||
4766 | }, | ||
4767 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4768 | "id": "feature.planSelection.cta.upgradePersonal", | ||
4769 | "start": { | ||
4770 | "column": 23, | ||
4771 | "line": 49 | ||
4772 | } | ||
4773 | }, | ||
4774 | { | ||
4775 | "defaultMessage": "!!!Choose Professional", | ||
4776 | "end": { | ||
4777 | "column": 3, | ||
4778 | "line": 56 | ||
4779 | }, | ||
4780 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4781 | "id": "feature.planSelection.cta.upgradePro", | ||
4782 | "start": { | ||
4783 | "column": 18, | ||
4784 | "line": 53 | ||
4785 | } | ||
4786 | }, | ||
4787 | { | ||
4788 | "defaultMessage": "!!!Complete comparison of all plans", | ||
4789 | "end": { | ||
4790 | "column": 3, | ||
4791 | "line": 60 | ||
4792 | }, | ||
4793 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4794 | "id": "feature.planSelection.fullFeatureList", | ||
4795 | "start": { | ||
4796 | "column": 19, | ||
4797 | "line": 57 | ||
4798 | } | ||
4799 | }, | ||
4800 | { | ||
4801 | "defaultMessage": "!!!All prices based on yearly payment", | ||
4802 | "end": { | ||
4803 | "column": 3, | ||
4804 | "line": 64 | ||
4805 | }, | ||
4806 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
4807 | "id": "feature.planSelection.pricesBasedOnAnnualPayment", | ||
4808 | "start": { | ||
4809 | "column": 30, | ||
4810 | "line": 61 | ||
4811 | } | ||
4812 | } | ||
4813 | ], | ||
4814 | "path": "src/features/planSelection/components/PlanSelection.json" | ||
4815 | }, | ||
4816 | { | ||
4817 | "descriptors": [ | ||
4818 | { | ||
4819 | "defaultMessage": "!!!per {interval}", | ||
4820 | "end": { | ||
4821 | "column": 3, | ||
4822 | "line": 19 | ||
4823 | }, | ||
4824 | "file": "src/features/planSelection/components/PlanTeaser.js", | ||
4825 | "id": "subscription.interval.per", | ||
4826 | "start": { | ||
4827 | "column": 7, | ||
4828 | "line": 16 | ||
4829 | } | ||
4830 | }, | ||
4831 | { | ||
4832 | "defaultMessage": "!!!Upgrade Account", | ||
4833 | "end": { | ||
4834 | "column": 3, | ||
4835 | "line": 23 | ||
4836 | }, | ||
4837 | "file": "src/features/planSelection/components/PlanTeaser.js", | ||
4838 | "id": "subscription.planItem.upgradeAccount", | ||
4839 | "start": { | ||
4840 | "column": 7, | ||
4841 | "line": 20 | ||
4842 | } | ||
4843 | } | ||
4844 | ], | ||
4845 | "path": "src/features/planSelection/components/PlanTeaser.json" | ||
4846 | }, | ||
4847 | { | ||
4848 | "descriptors": [ | ||
4849 | { | ||
4850 | "defaultMessage": "!!!Downgrade your Franz Plan", | ||
4851 | "end": { | ||
4852 | "column": 3, | ||
4853 | "line": 19 | ||
4854 | }, | ||
4855 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
4856 | "id": "feature.planSelection.fullscreen.dialog.title", | ||
4857 | "start": { | ||
4858 | "column": 15, | ||
4859 | "line": 16 | ||
4860 | } | ||
4861 | }, | ||
4862 | { | ||
4863 | "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
4864 | "end": { | ||
4865 | "column": 3, | ||
4866 | "line": 23 | ||
4867 | }, | ||
4868 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
4869 | "id": "feature.planSelection.fullscreen.dialog.message", | ||
4870 | "start": { | ||
4871 | "column": 17, | ||
4872 | "line": 20 | ||
4873 | } | ||
4874 | }, | ||
4875 | { | ||
4876 | "defaultMessage": "!!!Downgrade to Free", | ||
4877 | "end": { | ||
4878 | "column": 3, | ||
4879 | "line": 27 | ||
4880 | }, | ||
4881 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
4882 | "id": "feature.planSelection.fullscreen.dialog.cta.downgrade", | ||
4883 | "start": { | ||
4884 | "column": 22, | ||
4885 | "line": 24 | ||
4886 | } | ||
4887 | }, | ||
4888 | { | ||
4889 | "defaultMessage": "!!!Choose Personal", | ||
4890 | "end": { | ||
4891 | "column": 3, | ||
4892 | "line": 31 | ||
4893 | }, | ||
4894 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
4895 | "id": "feature.planSelection.fullscreen.dialog.cta.upgrade", | ||
4896 | "start": { | ||
4897 | "column": 20, | ||
4898 | "line": 28 | ||
4899 | } | ||
4900 | } | ||
4901 | ], | ||
4902 | "path": "src/features/planSelection/containers/PlanSelectionScreen.json" | ||
4903 | }, | ||
4904 | { | ||
4905 | "descriptors": [ | ||
4906 | { | ||
4497 | "defaultMessage": "!!!QuickSwitch", | 4907 | "defaultMessage": "!!!QuickSwitch", |
4498 | "end": { | 4908 | "end": { |
4499 | "column": 3, | 4909 | "column": 3, |
@@ -4727,6 +5137,107 @@ | |||
4727 | { | 5137 | { |
4728 | "descriptors": [ | 5138 | "descriptors": [ |
4729 | { | 5139 | { |
5140 | "defaultMessage": "!!!Your Free Franz {plan} Trial ends in {time}.", | ||
5141 | "end": { | ||
5142 | "column": 3, | ||
5143 | "line": 16 | ||
5144 | }, | ||
5145 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
5146 | "id": "feature.trialStatusBar.restTime", | ||
5147 | "start": { | ||
5148 | "column": 12, | ||
5149 | "line": 13 | ||
5150 | } | ||
5151 | }, | ||
5152 | { | ||
5153 | "defaultMessage": "!!!Your free Franz {plan} Trial has expired, please upgrade your account.", | ||
5154 | "end": { | ||
5155 | "column": 3, | ||
5156 | "line": 20 | ||
5157 | }, | ||
5158 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
5159 | "id": "feature.trialStatusBar.expired", | ||
5160 | "start": { | ||
5161 | "column": 11, | ||
5162 | "line": 17 | ||
5163 | } | ||
5164 | }, | ||
5165 | { | ||
5166 | "defaultMessage": "!!!Upgrade now", | ||
5167 | "end": { | ||
5168 | "column": 3, | ||
5169 | "line": 24 | ||
5170 | }, | ||
5171 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
5172 | "id": "feature.trialStatusBar.cta", | ||
5173 | "start": { | ||
5174 | "column": 7, | ||
5175 | "line": 21 | ||
5176 | } | ||
5177 | } | ||
5178 | ], | ||
5179 | "path": "src/features/trialStatusBar/components/TrialStatusBar.json" | ||
5180 | }, | ||
5181 | { | ||
5182 | "descriptors": [ | ||
5183 | { | ||
5184 | "defaultMessage": "!!!Downgrade your Franz Plan", | ||
5185 | "end": { | ||
5186 | "column": 3, | ||
5187 | "line": 19 | ||
5188 | }, | ||
5189 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
5190 | "id": "feature.trialStatusBar.fullscreen.dialog.title", | ||
5191 | "start": { | ||
5192 | "column": 15, | ||
5193 | "line": 16 | ||
5194 | } | ||
5195 | }, | ||
5196 | { | ||
5197 | "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
5198 | "end": { | ||
5199 | "column": 3, | ||
5200 | "line": 23 | ||
5201 | }, | ||
5202 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
5203 | "id": "feature.trialStatusBar.fullscreen.dialog.message", | ||
5204 | "start": { | ||
5205 | "column": 17, | ||
5206 | "line": 20 | ||
5207 | } | ||
5208 | }, | ||
5209 | { | ||
5210 | "defaultMessage": "!!!Downgrade to Free", | ||
5211 | "end": { | ||
5212 | "column": 3, | ||
5213 | "line": 27 | ||
5214 | }, | ||
5215 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
5216 | "id": "feature.trialStatusBar.fullscreen.dialog.cta.downgrade", | ||
5217 | "start": { | ||
5218 | "column": 22, | ||
5219 | "line": 24 | ||
5220 | } | ||
5221 | }, | ||
5222 | { | ||
5223 | "defaultMessage": "!!!Choose Personal", | ||
5224 | "end": { | ||
5225 | "column": 3, | ||
5226 | "line": 31 | ||
5227 | }, | ||
5228 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
5229 | "id": "feature.trialStatusBar.fullscreen.dialog.cta.upgrade", | ||
5230 | "start": { | ||
5231 | "column": 20, | ||
5232 | "line": 28 | ||
5233 | } | ||
5234 | } | ||
5235 | ], | ||
5236 | "path": "src/features/trialStatusBar/containers/TrialStatusBarScreen.json" | ||
5237 | }, | ||
5238 | { | ||
5239 | "descriptors": [ | ||
5240 | { | ||
4730 | "defaultMessage": "!!!Home", | 5241 | "defaultMessage": "!!!Home", |
4731 | "end": { | 5242 | "end": { |
4732 | "column": 3, | 5243 | "column": 3, |
@@ -5093,104 +5604,104 @@ | |||
5093 | "defaultMessage": "!!!Your workspaces", | 5604 | "defaultMessage": "!!!Your workspaces", |
5094 | "end": { | 5605 | "end": { |
5095 | "column": 3, | 5606 | "column": 3, |
5096 | "line": 22 | 5607 | "line": 23 |
5097 | }, | 5608 | }, |
5098 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5609 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5099 | "id": "settings.workspaces.headline", | 5610 | "id": "settings.workspaces.headline", |
5100 | "start": { | 5611 | "start": { |
5101 | "column": 12, | 5612 | "column": 12, |
5102 | "line": 19 | 5613 | "line": 20 |
5103 | } | 5614 | } |
5104 | }, | 5615 | }, |
5105 | { | 5616 | { |
5106 | "defaultMessage": "!!!You haven't added any workspaces yet.", | 5617 | "defaultMessage": "!!!You haven't added any workspaces yet.", |
5107 | "end": { | 5618 | "end": { |
5108 | "column": 3, | 5619 | "column": 3, |
5109 | "line": 26 | 5620 | "line": 27 |
5110 | }, | 5621 | }, |
5111 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5622 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5112 | "id": "settings.workspaces.noWorkspacesAdded", | 5623 | "id": "settings.workspaces.noWorkspacesAdded", |
5113 | "start": { | 5624 | "start": { |
5114 | "column": 19, | 5625 | "column": 19, |
5115 | "line": 23 | 5626 | "line": 24 |
5116 | } | 5627 | } |
5117 | }, | 5628 | }, |
5118 | { | 5629 | { |
5119 | "defaultMessage": "!!!Could not load your workspaces", | 5630 | "defaultMessage": "!!!Could not load your workspaces", |
5120 | "end": { | 5631 | "end": { |
5121 | "column": 3, | 5632 | "column": 3, |
5122 | "line": 30 | 5633 | "line": 31 |
5123 | }, | 5634 | }, |
5124 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5635 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5125 | "id": "settings.workspaces.workspacesRequestFailed", | 5636 | "id": "settings.workspaces.workspacesRequestFailed", |
5126 | "start": { | 5637 | "start": { |
5127 | "column": 27, | 5638 | "column": 27, |
5128 | "line": 27 | 5639 | "line": 28 |
5129 | } | 5640 | } |
5130 | }, | 5641 | }, |
5131 | { | 5642 | { |
5132 | "defaultMessage": "!!!Try again", | 5643 | "defaultMessage": "!!!Try again", |
5133 | "end": { | 5644 | "end": { |
5134 | "column": 3, | 5645 | "column": 3, |
5135 | "line": 34 | 5646 | "line": 35 |
5136 | }, | 5647 | }, |
5137 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5648 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5138 | "id": "settings.workspaces.tryReloadWorkspaces", | 5649 | "id": "settings.workspaces.tryReloadWorkspaces", |
5139 | "start": { | 5650 | "start": { |
5140 | "column": 23, | 5651 | "column": 23, |
5141 | "line": 31 | 5652 | "line": 32 |
5142 | } | 5653 | } |
5143 | }, | 5654 | }, |
5144 | { | 5655 | { |
5145 | "defaultMessage": "!!!Your changes have been saved", | 5656 | "defaultMessage": "!!!Your changes have been saved", |
5146 | "end": { | 5657 | "end": { |
5147 | "column": 3, | 5658 | "column": 3, |
5148 | "line": 38 | 5659 | "line": 39 |
5149 | }, | 5660 | }, |
5150 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5661 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5151 | "id": "settings.workspaces.updatedInfo", | 5662 | "id": "settings.workspaces.updatedInfo", |
5152 | "start": { | 5663 | "start": { |
5153 | "column": 15, | 5664 | "column": 15, |
5154 | "line": 35 | 5665 | "line": 36 |
5155 | } | 5666 | } |
5156 | }, | 5667 | }, |
5157 | { | 5668 | { |
5158 | "defaultMessage": "!!!Workspace has been deleted", | 5669 | "defaultMessage": "!!!Workspace has been deleted", |
5159 | "end": { | 5670 | "end": { |
5160 | "column": 3, | 5671 | "column": 3, |
5161 | "line": 42 | 5672 | "line": 43 |
5162 | }, | 5673 | }, |
5163 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5674 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5164 | "id": "settings.workspaces.deletedInfo", | 5675 | "id": "settings.workspaces.deletedInfo", |
5165 | "start": { | 5676 | "start": { |
5166 | "column": 15, | 5677 | "column": 15, |
5167 | "line": 39 | 5678 | "line": 40 |
5168 | } | 5679 | } |
5169 | }, | 5680 | }, |
5170 | { | 5681 | { |
5171 | "defaultMessage": "!!!Info about workspace feature", | 5682 | "defaultMessage": "!!!Info about workspace feature", |
5172 | "end": { | 5683 | "end": { |
5173 | "column": 3, | 5684 | "column": 3, |
5174 | "line": 46 | 5685 | "line": 47 |
5175 | }, | 5686 | }, |
5176 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5687 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5177 | "id": "settings.workspaces.workspaceFeatureInfo", | 5688 | "id": "settings.workspaces.workspaceFeatureInfo", |
5178 | "start": { | 5689 | "start": { |
5179 | "column": 24, | 5690 | "column": 24, |
5180 | "line": 43 | 5691 | "line": 44 |
5181 | } | 5692 | } |
5182 | }, | 5693 | }, |
5183 | { | 5694 | { |
5184 | "defaultMessage": "!!!Less is More: Introducing Ferdi Workspaces", | 5695 | "defaultMessage": "!!!Less is More: Introducing Ferdi Workspaces", |
5185 | "end": { | 5696 | "end": { |
5186 | "column": 3, | 5697 | "column": 3, |
5187 | "line": 50 | 5698 | "line": 51 |
5188 | }, | 5699 | }, |
5189 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5700 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
5190 | "id": "settings.workspaces.workspaceFeatureHeadline", | 5701 | "id": "settings.workspaces.workspaceFeatureHeadline", |
5191 | "start": { | 5702 | "start": { |
5192 | "column": 28, | 5703 | "column": 28, |
5193 | "line": 47 | 5704 | "line": 48 |
5194 | } | 5705 | } |
5195 | } | 5706 | } |
5196 | ], | 5707 | ], |
@@ -5217,7 +5728,7 @@ | |||
5217 | { | 5728 | { |
5218 | "descriptors": [ | 5729 | "descriptors": [ |
5219 | { | 5730 | { |
5220 | "defaultMessage": "!!!Franz Professional", | 5731 | "defaultMessage": "!!!Professional", |
5221 | "end": { | 5732 | "end": { |
5222 | "column": 3, | 5733 | "column": 3, |
5223 | "line": 8 | 5734 | "line": 8 |
@@ -5230,7 +5741,7 @@ | |||
5230 | } | 5741 | } |
5231 | }, | 5742 | }, |
5232 | { | 5743 | { |
5233 | "defaultMessage": "!!!Franz Personal", | 5744 | "defaultMessage": "!!!Personal", |
5234 | "end": { | 5745 | "end": { |
5235 | "column": 3, | 5746 | "column": 3, |
5236 | "line": 12 | 5747 | "line": 12 |
@@ -5243,7 +5754,7 @@ | |||
5243 | } | 5754 | } |
5244 | }, | 5755 | }, |
5245 | { | 5756 | { |
5246 | "defaultMessage": "!!!Franz Free", | 5757 | "defaultMessage": "!!!Free", |
5247 | "end": { | 5758 | "end": { |
5248 | "column": 3, | 5759 | "column": 3, |
5249 | "line": 16 | 5760 | "line": 16 |
@@ -5256,7 +5767,7 @@ | |||
5256 | } | 5767 | } |
5257 | }, | 5768 | }, |
5258 | { | 5769 | { |
5259 | "defaultMessage": "!!!Franz Premium", | 5770 | "defaultMessage": "!!!Premium", |
5260 | "end": { | 5771 | "end": { |
5261 | "column": 3, | 5772 | "column": 3, |
5262 | "line": 20 | 5773 | "line": 20 |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 477bdf43c..a34da1848 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -9,6 +9,22 @@ | |||
9 | "feature.delayApp.trial.headline": "Get the free Ferdi Professional 14 day trial and skip the line", | 9 | "feature.delayApp.trial.headline": "Get the free Ferdi Professional 14 day trial and skip the line", |
10 | "feature.delayApp.upgrade.action": "Get a Ferdi Supporter License", | 10 | "feature.delayApp.upgrade.action": "Get a Ferdi Supporter License", |
11 | "feature.delayApp.upgrade.actionShort": "Upgrade account", | 11 | "feature.delayApp.upgrade.actionShort": "Upgrade account", |
12 | "feature.planSelection.cta.ctaDowngradeFree": "Downgrade to Free", | ||
13 | "feature.planSelection.cta.stayOnFree": "Stay on Free", | ||
14 | "feature.planSelection.cta.trial": "Start my free 14-days Trial", | ||
15 | "feature.planSelection.cta.upgradePersonal": "Choose Personal", | ||
16 | "feature.planSelection.cta.upgradePro": "Choose Professional", | ||
17 | "feature.planSelection.free.text": "Basic functionality", | ||
18 | "feature.planSelection.fullFeatureList": "Complete comparison of all plans", | ||
19 | "feature.planSelection.fullscreen.dialog.cta.downgrade": "Downgrade to Free", | ||
20 | "feature.planSelection.fullscreen.dialog.cta.upgrade": "Choose Personal", | ||
21 | "feature.planSelection.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
22 | "feature.planSelection.fullscreen.dialog.title": "Downgrade your Ferdi Plan", | ||
23 | "feature.planSelection.fullscreen.subheadline": "It's time to make a choice. Ferdi works best on our Personal and Professional plans. Please have a look and choose the best one for you.", | ||
24 | "feature.planSelection.fullscreen.welcome": "Are you ready to choose, {name}", | ||
25 | "feature.planSelection.personal.text": "More services, no waiting - ideal for personal use.", | ||
26 | "feature.planSelection.pricesBasedOnAnnualPayment": "All prices based on yearly payment", | ||
27 | "feature.planSelection.pro.text": "Unlimited services and professional features for you - and your team.", | ||
12 | "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.", | 28 | "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.", |
13 | "feature.quickSwitch.search": "Search...", | 29 | "feature.quickSwitch.search": "Search...", |
14 | "feature.quickSwitch.title": "QuickSwitch", | 30 | "feature.quickSwitch.title": "QuickSwitch", |
@@ -23,6 +39,13 @@ | |||
23 | "feature.todos.premium.info": "Ferdi Todos are available to premium users now!", | 39 | "feature.todos.premium.info": "Ferdi Todos are available to premium users now!", |
24 | "feature.todos.premium.rollout": "Everyone else will have to wait a little longer.", | 40 | "feature.todos.premium.rollout": "Everyone else will have to wait a little longer.", |
25 | "feature.todos.premium.upgrade": "Upgrade Account", | 41 | "feature.todos.premium.upgrade": "Upgrade Account", |
42 | "feature.trialStatusBar.cta": "Upgrade now", | ||
43 | "feature.trialStatusBar.expired": "Your free Ferdi {plan} Trial has expired, please upgrade your account.", | ||
44 | "feature.trialStatusBar.fullscreen.dialog.cta.downgrade": "Downgrade to Free", | ||
45 | "feature.trialStatusBar.fullscreen.dialog.cta.upgrade": "Choose Personal", | ||
46 | "feature.trialStatusBar.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
47 | "feature.trialStatusBar.fullscreen.dialog.title": "Downgrade your Ferdi Plan", | ||
48 | "feature.trialStatusBar.restTime": "Your Free Ferdi {plan} Trial ends in {time}.", | ||
26 | "global.api.unhealthy": "Can't connect to Ferdi online services", | 49 | "global.api.unhealthy": "Can't connect to Ferdi online services", |
27 | "global.franzProRequired": "Ferdi Professional Required", | 50 | "global.franzProRequired": "Ferdi Professional Required", |
28 | "global.notConnectedToTheInternet": "You are not connected to the internet.", | 51 | "global.notConnectedToTheInternet": "You are not connected to the internet.", |
@@ -140,15 +163,20 @@ | |||
140 | "password.submit.label": "Submit", | 163 | "password.submit.label": "Submit", |
141 | "password.successInfo": "Please check your email", | 164 | "password.successInfo": "Please check your email", |
142 | "premiumFeature.button.upgradeAccount": "Upgrade account", | 165 | "premiumFeature.button.upgradeAccount": "Upgrade account", |
166 | "pricing.features.accountSync": "Account Synchronisation", | ||
143 | "pricing.features.adFree": "Forever ad-free", | 167 | "pricing.features.adFree": "Forever ad-free", |
144 | "pricing.features.appDelays": "No Waiting Screens", | 168 | "pricing.features.appDelays": "No Waiting Screens", |
145 | "pricing.features.customWebsites": "Add Custom Websites", | 169 | "pricing.features.customWebsites": "Add Custom Websites", |
170 | "pricing.features.desktopNotifications": "Desktop Notifications", | ||
146 | "pricing.features.onPremise": "On-premise & other Hosted Services", | 171 | "pricing.features.onPremise": "On-premise & other Hosted Services", |
172 | "pricing.features.recipes": "Choose from more than 70 Services", | ||
147 | "pricing.features.serviceProxies": "Service Proxies", | 173 | "pricing.features.serviceProxies": "Service Proxies", |
148 | "pricing.features.spellchecker": "Spellchecker support", | 174 | "pricing.features.spellchecker": "Spellchecker support", |
149 | "pricing.features.teamManagement": "Team Management", | 175 | "pricing.features.teamManagement": "Team Management", |
150 | "pricing.features.thirdPartyServices": "Install 3rd party services", | 176 | "pricing.features.thirdPartyServices": "Install 3rd party services", |
151 | "pricing.features.unlimitedServices": "Add unlimited services", | 177 | "pricing.features.unlimitedServices": "Add unlimited services", |
178 | "pricing.features.upToSixServices": "Add up to 6 services", | ||
179 | "pricing.features.upToThreeServices": "Add up to 3 services", | ||
152 | "pricing.features.workspaces": "Workspaces", | 180 | "pricing.features.workspaces": "Workspaces", |
153 | "pricing.plan.free": "Ferdi Free", | 181 | "pricing.plan.free": "Ferdi Free", |
154 | "pricing.plan.legacy": "Ferdi Premium", | 182 | "pricing.plan.legacy": "Ferdi Premium", |
@@ -160,13 +188,17 @@ | |||
160 | "pricing.plan.pro-yearly": "Ferdi Professional Yearly", | 188 | "pricing.plan.pro-yearly": "Ferdi Professional Yearly", |
161 | "pricing.trial.cta.accept": "Yes, upgrade my account to Ferdi Professional", | 189 | "pricing.trial.cta.accept": "Yes, upgrade my account to Ferdi Professional", |
162 | "pricing.trial.cta.skip": "Continue to Ferdi", | 190 | "pricing.trial.cta.skip": "Continue to Ferdi", |
191 | "pricing.trial.cta.start": "Start using Ferdi", | ||
163 | "pricing.trial.error": "Sorry, we could not activate your trial!", | 192 | "pricing.trial.error": "Sorry, we could not activate your trial!", |
164 | "pricing.trial.features.headline": "Ferdi Professional includes:", | 193 | "pricing.trial.features.headline": "Ferdi Professional includes:", |
165 | "pricing.trial.headline": "Ferdi Professional", | 194 | "pricing.trial.headline.pro": "Hi {name}, welcome to Ferdi", |
166 | "pricing.trial.subheadline": "Your personal welcome offer:", | 195 | "pricing.trial.intro.happyMessaging": "Happy messaging,", |
196 | "pricing.trial.intro.specialTreat": "We have a special treat for you.", | ||
197 | "pricing.trial.intro.tryPro": "Enjoy the full Ferdi Professional experience completely free for 14 days.", | ||
167 | "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days", | 198 | "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days", |
168 | "pricing.trial.terms.headline": "No strings attached", | 199 | "pricing.trial.terms.headline": "No strings attached", |
169 | "pricing.trial.terms.noCreditCard": "No credit card required", | 200 | "pricing.trial.terms.noCreditCard": "No credit card required", |
201 | "pricing.trial.terms.trialWorth": "Free trial (normally {currency}{price} per month)", | ||
170 | "service.crashHandler.action": "Reload {name}", | 202 | "service.crashHandler.action": "Reload {name}", |
171 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", | 203 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", |
172 | "service.crashHandler.headline": "Oh no!", | 204 | "service.crashHandler.headline": "Oh no!", |
@@ -405,10 +437,15 @@ | |||
405 | "signup.link.login": "Already have an account, sign in?", | 437 | "signup.link.login": "Already have an account, sign in?", |
406 | "signup.password.label": "Password", | 438 | "signup.password.label": "Password", |
407 | "signup.submit.label": "Create account", | 439 | "signup.submit.label": "Create account", |
440 | "subscription.bestValue": "Best value", | ||
408 | "subscription.cta.activateTrial": "Yes, start the free Ferdi Professional trial", | 441 | "subscription.cta.activateTrial": "Yes, start the free Ferdi Professional trial", |
409 | "subscription.cta.allOptions": "See all options", | 442 | "subscription.cta.allOptions": "See all options", |
410 | "subscription.cta.choosePlan": "Choose your plan", | 443 | "subscription.cta.choosePlan": "Choose your plan", |
411 | "subscription.includedProFeatures": "The Ferdi Professional Plan includes:", | 444 | "subscription.includedProFeatures": "The Ferdi Professional Plan includes:", |
445 | "subscription.interval.per": "per {interval}", | ||
446 | "subscription.interval.perMonth": "per month", | ||
447 | "subscription.interval.perMonthPerUser": "per month & user", | ||
448 | "subscription.planItem.upgradeAccount": "Upgrade Account", | ||
412 | "subscription.teaser.includedFeatures": "Paid Ferdi Plans include:", | 449 | "subscription.teaser.includedFeatures": "Paid Ferdi Plans include:", |
413 | "subscription.teaser.intro": "Ferdi 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!", | 450 | "subscription.teaser.intro": "Ferdi 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!", |
414 | "subscriptionPopup.buttonCancel": "Cancel", | 451 | "subscriptionPopup.buttonCancel": "Cancel", |
diff --git a/src/i18n/locales/zh-Hant.json b/src/i18n/locales/zh-Hant.json deleted file mode 100644 index 872bfd9d7..000000000 --- a/src/i18n/locales/zh-Hant.json +++ /dev/null | |||
@@ -1,485 +0,0 @@ | |||
1 | { | ||
2 | "app.errorHandler.action": "Reload", | ||
3 | "app.errorHandler.headline": "Something went wrong", | ||
4 | "feature.announcements.changelog.headline": "Changes in Ferdi {version}", | ||
5 | "feature.delayApp.headline": "Please purchase a Ferdi Supporter License to skip waiting", | ||
6 | "feature.delayApp.text": "Ferdi will continue in {seconds} seconds.", | ||
7 | "feature.delayApp.trial.action": "Yes, I want the free 14 day trial of Ferdi Professional", | ||
8 | "feature.delayApp.trial.actionShort": "Activate the free Ferdi Professional trial", | ||
9 | "feature.delayApp.trial.headline": "Get the free Ferdi Professional 14 day trial and skip the line", | ||
10 | "feature.delayApp.upgrade.action": "Get a Ferdi Supporter License", | ||
11 | "feature.delayApp.upgrade.actionShort": "Upgrade account", | ||
12 | "feature.planSelection.cta.ctaDowngradeFree": "Downgrade to Free", | ||
13 | "feature.planSelection.cta.stayOnFree": "Stay on Free", | ||
14 | "feature.planSelection.cta.trial": "Start my free 14-days Trial", | ||
15 | "feature.planSelection.cta.upgradePersonal": "Choose Personal", | ||
16 | "feature.planSelection.cta.upgradePro": "Choose Professional", | ||
17 | "feature.planSelection.free.text": "Basic functionality", | ||
18 | "feature.planSelection.fullFeatureList": "Complete comparison of all plans", | ||
19 | "feature.planSelection.fullscreen.dialog.cta.downgrade": "Downgrade to Free", | ||
20 | "feature.planSelection.fullscreen.dialog.cta.upgrade": "Choose Personal", | ||
21 | "feature.planSelection.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
22 | "feature.planSelection.fullscreen.dialog.title": "Downgrade your Ferdi Plan", | ||
23 | "feature.planSelection.fullscreen.subheadline": "It's time to make a choice. Ferdi works best on our Personal and Professional plans. Please have a look and choose the best one for you.", | ||
24 | "feature.planSelection.fullscreen.welcome": "Are you ready to choose, {name}", | ||
25 | "feature.planSelection.personal.text": "More services, no waiting - ideal for personal use.", | ||
26 | "feature.planSelection.pricesBasedOnAnnualPayment": "All prices based on yearly payment", | ||
27 | "feature.planSelection.pro.text": "Unlimited services and professional features for you - and your team.", | ||
28 | "feature.quickSwitch.info": "Select a service with TAB, ↑ and ↓. Open a service with ENTER.", | ||
29 | "feature.quickSwitch.search": "Search...", | ||
30 | "feature.quickSwitch.title": "QuickSwitch", | ||
31 | "feature.serviceLimit.limitReached": "You have added {amount} out of {limit} services that are included in your plan. Please upgrade your account to add more services.", | ||
32 | "feature.shareFranz.action.email": "Send as email", | ||
33 | "feature.shareFranz.action.facebook": "Share on Facebook", | ||
34 | "feature.shareFranz.action.twitter": "Share on Twitter", | ||
35 | "feature.shareFranz.headline": "Ferdi is better together!", | ||
36 | "feature.shareFranz.shareText.email": "I've added {count} services to Ferdi! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.getferdi.com", | ||
37 | "feature.shareFranz.shareText.twitter": "I've added {count} services to Ferdi! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.getferdi.com /cc @FerdiMessenger", | ||
38 | "feature.shareFranz.text": "Tell your friends and colleagues how awesome Ferdi is and help us to spread the word.", | ||
39 | "feature.todos.premium.info": "Ferdi Todos are available to premium users now!", | ||
40 | "feature.todos.premium.rollout": "Everyone else will have to wait a little longer.", | ||
41 | "feature.todos.premium.upgrade": "Upgrade Account", | ||
42 | "feature.trialStatusBar.cta": "Upgrade now", | ||
43 | "feature.trialStatusBar.expired": "Your free Ferdi {plan} Trial has expired, please upgrade your account.", | ||
44 | "feature.trialStatusBar.fullscreen.dialog.cta.downgrade": "Downgrade to Free", | ||
45 | "feature.trialStatusBar.fullscreen.dialog.cta.upgrade": "Choose Personal", | ||
46 | "feature.trialStatusBar.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
47 | "feature.trialStatusBar.fullscreen.dialog.title": "Downgrade your Ferdi Plan", | ||
48 | "feature.trialStatusBar.restTime": "Your Free Ferdi {plan} Trial ends in {time}.", | ||
49 | "global.api.unhealthy": "無法連接到Ferdi網路服務", | ||
50 | "global.franzProRequired": "Ferdi Professional Required", | ||
51 | "global.notConnectedToTheInternet": "您未連上網際網路", | ||
52 | "global.spellchecker.useDefault": "Use System Default ({default})", | ||
53 | "global.spellchecking.autodetect": "Detect language automatically", | ||
54 | "global.spellchecking.autodetect.short": "Automatic", | ||
55 | "global.spellchecking.language": "Spell checking language", | ||
56 | "global.upgradeButton.upgradeToPro": "Upgrade to Ferdi Professional", | ||
57 | "import.headline": "匯入您的 Ferdi 4 服務", | ||
58 | "import.notSupportedHeadline": "此服務不被 Ferdi 5 支持", | ||
59 | "import.skip.label": "我想手動匯入", | ||
60 | "import.submit.label": "匯入服務", | ||
61 | "infobar.authRequestFailed": "There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.", | ||
62 | "infobar.buttonChangelog": "What is new?", | ||
63 | "infobar.buttonInstallUpdate": "重新啟動並且更新", | ||
64 | "infobar.buttonReloadServices": "重新載入", | ||
65 | "infobar.requiredRequestsFailed": "無法載入服務與帳戶資訊", | ||
66 | "infobar.servicesUpdated": "您的服務已更新", | ||
67 | "infobar.trialActivated": "Your trial was successfully activated. Happy messaging!", | ||
68 | "infobar.updateAvailable": "有新的更新可安裝", | ||
69 | "invite.email.label": "電子郵件信箱", | ||
70 | "invite.headline.friends": "邀請三個人", | ||
71 | "invite.name.label": "名子", | ||
72 | "invite.skip.label": "我想晚點進行", | ||
73 | "invite.submit.label": "Send invites", | ||
74 | "invite.successInfo": "Invitations sent successfully", | ||
75 | "locked.headline": "Locked", | ||
76 | "locked.info": "Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.", | ||
77 | "locked.invalidCredentials": "Password invalid", | ||
78 | "locked.password.label": "Password", | ||
79 | "locked.submit.label": "Unlock", | ||
80 | "login.changeServer": "Change server", | ||
81 | "login.customServerQuestion": "Using a custom Ferdi server?", | ||
82 | "login.customServerSuggestion": "Try importing your Franz account", | ||
83 | "login.email.label": "電子郵件信箱", | ||
84 | "login.headline": "登入", | ||
85 | "login.invalidCredentials": "電子郵件帳戶或密碼有誤", | ||
86 | "login.link.password": "密碼重設", | ||
87 | "login.link.signup": "建立一個免費帳戶", | ||
88 | "login.password.label": "Password", | ||
89 | "login.serverLogout": "登入狀態過期,請重新登入", | ||
90 | "login.submit.label": "登入", | ||
91 | "login.tokenExpired": "登入狀態過期,請重新登入", | ||
92 | "menu.Todoss.closeTodosDrawer": "Close Todos drawer", | ||
93 | "menu.Todoss.openTodosDrawer": "Open Todos drawer", | ||
94 | "menu.app.about": "About Ferdi", | ||
95 | "menu.app.announcement": "What's new?", | ||
96 | "menu.app.autohideMenuBar": "Auto-hide menu bar", | ||
97 | "menu.app.checkForUpdates": "Check for updates", | ||
98 | "menu.app.hide": "Hide", | ||
99 | "menu.app.hideOthers": "Hide Others", | ||
100 | "menu.app.quit": "Quit", | ||
101 | "menu.app.settings": "Settings", | ||
102 | "menu.app.unhide": "Unhide", | ||
103 | "menu.edit": "Edit", | ||
104 | "menu.edit.copy": "Copy", | ||
105 | "menu.edit.cut": "Cut", | ||
106 | "menu.edit.delete": "Delete", | ||
107 | "menu.edit.emojiSymbols": "Emoji & Symbols", | ||
108 | "menu.edit.paste": "Paste", | ||
109 | "menu.edit.pasteAndMatchStyle": "Paste And Match Style", | ||
110 | "menu.edit.redo": "Redo", | ||
111 | "menu.edit.selectAll": "Select All", | ||
112 | "menu.edit.speech": "Speech", | ||
113 | "menu.edit.startDictation": "Start Dictation", | ||
114 | "menu.edit.startSpeaking": "Start Speaking", | ||
115 | "menu.edit.stopSpeaking": "Stop Speaking", | ||
116 | "menu.edit.undo": "Undo", | ||
117 | "menu.file": "File", | ||
118 | "menu.help": "Help", | ||
119 | "menu.help.changelog": "Changelog", | ||
120 | "menu.help.debugInfo": "Copy Debug Information", | ||
121 | "menu.help.debugInfoCopiedBody": "Your Debug Information has been copied to your clipboard.", | ||
122 | "menu.help.debugInfoCopiedHeadline": "Ferdi Debug Information", | ||
123 | "menu.help.learnMore": "Learn More", | ||
124 | "menu.help.privacy": "Privacy Statement", | ||
125 | "menu.help.support": "Support", | ||
126 | "menu.help.tos": "Terms of Service", | ||
127 | "menu.services": "Services", | ||
128 | "menu.services.activatePreviousService": "Activate previous service", | ||
129 | "menu.services.addNewService": "Add New Service...", | ||
130 | "menu.services.goHome": "Home", | ||
131 | "menu.services.setNextServiceActive": "Activate next service", | ||
132 | "menu.todos": "Todos", | ||
133 | "menu.todos.enableTodos": "Enable Todos", | ||
134 | "menu.view": "View", | ||
135 | "menu.view.back": "Back", | ||
136 | "menu.view.enterFullScreen": "Enter Full Screen", | ||
137 | "menu.view.exitFullScreen": "Exit Full Screen", | ||
138 | "menu.view.forward": "Forward", | ||
139 | "menu.view.lockFerdi": "Lock Ferdi", | ||
140 | "menu.view.openQuickSwitch": "Open Quick Switch", | ||
141 | "menu.view.reloadFranz": "Reload Ferdi", | ||
142 | "menu.view.reloadService": "Reload Service", | ||
143 | "menu.view.resetZoom": "Actual Size", | ||
144 | "menu.view.toggleDevTools": "Toggle Developer Tools", | ||
145 | "menu.view.toggleFullScreen": "Toggle Full Screen", | ||
146 | "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools", | ||
147 | "menu.view.toggleTodosDevTools": "Toggle Todos Developer Tools", | ||
148 | "menu.view.zoomIn": "Zoom In", | ||
149 | "menu.view.zoomOut": "Zoom Out", | ||
150 | "menu.window": "Window", | ||
151 | "menu.window.close": "Close", | ||
152 | "menu.window.minimize": "Minimize", | ||
153 | "menu.workspaces": "Workspaces", | ||
154 | "menu.workspaces.addNewWorkspace": "Add New Workspace...", | ||
155 | "menu.workspaces.closeWorkspaceDrawer": "Close workspace drawer", | ||
156 | "menu.workspaces.defaultWorkspace": "All services", | ||
157 | "menu.workspaces.openWorkspaceDrawer": "Open workspace drawer", | ||
158 | "password.email.label": "電子郵件信箱", | ||
159 | "password.headline": "密碼重設", | ||
160 | "password.link.login": "登入您的帳戶", | ||
161 | "password.link.signup": "建立一個免費帳戶", | ||
162 | "password.noUser": "此電子郵件帳戶不存在", | ||
163 | "password.submit.label": "送出", | ||
164 | "password.successInfo": "請重新確認您的電子郵件信箱", | ||
165 | "premiumFeature.button.upgradeAccount": "Upgrade account", | ||
166 | "pricing.features.accountSync": "Account Synchronisation", | ||
167 | "pricing.features.adFree": "Forever ad-free", | ||
168 | "pricing.features.appDelays": "No Waiting Screens", | ||
169 | "pricing.features.customWebsites": "Add Custom Websites", | ||
170 | "pricing.features.desktopNotifications": "Desktop Notifications", | ||
171 | "pricing.features.onPremise": "On-premise & other Hosted Services", | ||
172 | "pricing.features.recipes": "Choose from more than 70 Services", | ||
173 | "pricing.features.serviceProxies": "Service Proxies", | ||
174 | "pricing.features.spellchecker": "Spellchecker support", | ||
175 | "pricing.features.teamManagement": "Team Management", | ||
176 | "pricing.features.thirdPartyServices": "Install 3rd party services", | ||
177 | "pricing.features.unlimitedServices": "Add unlimited services", | ||
178 | "pricing.features.upToSixServices": "Add up to 6 services", | ||
179 | "pricing.features.upToThreeServices": "Add up to 3 services", | ||
180 | "pricing.features.workspaces": "Workspaces", | ||
181 | "pricing.plan.free": "Ferdi Free", | ||
182 | "pricing.plan.legacy": "Ferdi Premium", | ||
183 | "pricing.plan.personal": "Ferdi Personal", | ||
184 | "pricing.plan.personal-monthly": "Ferdi Personal Monthly", | ||
185 | "pricing.plan.personal-yearly": "Ferdi Personal Yearly", | ||
186 | "pricing.plan.pro": "Ferdi Professional", | ||
187 | "pricing.plan.pro-monthly": "Ferdi Professional Monthly", | ||
188 | "pricing.plan.pro-yearly": "Ferdi Professional Yearly", | ||
189 | "pricing.trial.cta.accept": "Yes, upgrade my account to Ferdi Professional", | ||
190 | "pricing.trial.cta.skip": "Continue to Ferdi", | ||
191 | "pricing.trial.cta.start": "Start using Ferdi", | ||
192 | "pricing.trial.error": "Sorry, we could not activate your trial!", | ||
193 | "pricing.trial.features.headline": "Ferdi Professional includes:", | ||
194 | "pricing.trial.headline.pro": "Hi {name}, welcome to Ferdi", | ||
195 | "pricing.trial.intro.happyMessaging": "Happy messaging,", | ||
196 | "pricing.trial.intro.specialTreat": "We have a special treat for you.", | ||
197 | "pricing.trial.intro.tryPro": "Enjoy the full Ferdi Professional experience completely free for 14 days.", | ||
198 | "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days", | ||
199 | "pricing.trial.terms.headline": "No strings attached", | ||
200 | "pricing.trial.terms.noCreditCard": "No credit card required", | ||
201 | "pricing.trial.terms.trialWorth": "Free trial (normally {currency}{price} per month)", | ||
202 | "service.crashHandler.action": "Reload {name}", | ||
203 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", | ||
204 | "service.crashHandler.headline": "Oh no!", | ||
205 | "service.crashHandler.text": "{name} has caused an error.", | ||
206 | "service.disabledHandler.action": "Enable {name}", | ||
207 | "service.disabledHandler.headline": "{name} is disabled", | ||
208 | "service.errorHandler.action": "Reload {name}", | ||
209 | "service.errorHandler.editAction": "Edit {name}", | ||
210 | "service.errorHandler.headline": "Oh no!", | ||
211 | "service.errorHandler.message": "Error", | ||
212 | "service.errorHandler.text": "{name} has failed to load.", | ||
213 | "service.restrictedHandler.action": "Upgrade Account", | ||
214 | "service.restrictedHandler.customUrl.headline": "Ferdi Professional Plan required", | ||
215 | "service.restrictedHandler.customUrl.text": "Please upgrade to the Ferdi Professional plan to use custom urls & self hosted services.", | ||
216 | "service.restrictedHandler.serviceLimit.headline": "You have reached your service limit.", | ||
217 | "service.restrictedHandler.serviceLimit.text": "Please upgrade your account to use more than {count} services.", | ||
218 | "service.webviewLoader.loading": "Loading", | ||
219 | "services.getStarted": "開始使用", | ||
220 | "services.login": "Please login to use Ferdi.", | ||
221 | "services.serverInfo": "Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.", | ||
222 | "services.serverless": "Use Ferdi without an Account", | ||
223 | "services.welcome": "歡迎使用 Ferdi", | ||
224 | "settings.account.account.editButton": "更改帳戶資訊", | ||
225 | "settings.account.accountType.basic": "基本帳戶", | ||
226 | "settings.account.accountType.premium": "Premium Supporter Account", | ||
227 | "settings.account.buttonSave": "更新帳戶資訊", | ||
228 | "settings.account.deleteAccount": "Delete account", | ||
229 | "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", | ||
230 | "settings.account.deleteInfo": "If you don't need your Ferdi account any longer, you can delete your account and all related data here.", | ||
231 | "settings.account.headline": "帳戶", | ||
232 | "settings.account.headlineAccount": "帳戶資訊", | ||
233 | "settings.account.headlineDangerZone": "Danger Zone", | ||
234 | "settings.account.headlineInvoices": "Invoices", | ||
235 | "settings.account.headlinePassword": "更改密碼", | ||
236 | "settings.account.headlineProfile": "更新帳戶資訊", | ||
237 | "settings.account.headlineSubscription": "您的訂閱", | ||
238 | "settings.account.headlineTrialUpgrade": "Get the free 14 day Ferdi Professional Trial", | ||
239 | "settings.account.headlineUpgradeAccount": "Upgrade your account & get the full Ferdi experience", | ||
240 | "settings.account.invoiceDownload": "下載", | ||
241 | "settings.account.manageSubscription.label": "管理訂閱", | ||
242 | "settings.account.successInfo": "您的更改已經儲存", | ||
243 | "settings.account.trial": "Free Trial", | ||
244 | "settings.account.trialEndsIn": "Your free trial ends in {duration}.", | ||
245 | "settings.account.trialUpdateBillingInfo": "Please update your billing info to continue using {license} after your trial period.", | ||
246 | "settings.account.tryReloadServices": "Try again", | ||
247 | "settings.account.tryReloadUserInfoRequest": "Try again", | ||
248 | "settings.account.upgradeToPro.label": "Upgrade to Ferdi Professional", | ||
249 | "settings.account.userInfoRequestFailed": "無法載入帳戶資訊", | ||
250 | "settings.account.yourLicense": "Your Ferdi License", | ||
251 | "settings.app.accentColorInfo": "Write your accent color in a CSS-compatible format. (Default: #7367f0)", | ||
252 | "settings.app.buttonClearAllCache": "Clear cache", | ||
253 | "settings.app.buttonInstallUpdate": "重新啟動並且更新", | ||
254 | "settings.app.buttonSearchForUpdate": "Check for updates", | ||
255 | "settings.app.cacheInfo": "Ferdi cache is currently using {size} of disk space.", | ||
256 | "settings.app.currentVersion": "當前版本:", | ||
257 | "settings.app.form.accentColor": "Accent color", | ||
258 | "settings.app.form.autoLaunchInBackground": "背景啟動", | ||
259 | "settings.app.form.autoLaunchOnStart": "開機時啟動", | ||
260 | "settings.app.form.beta": "包含開發中版本", | ||
261 | "settings.app.form.darkMode": "Join the Dark Side", | ||
262 | "settings.app.form.enableGPUAcceleration": "Enable GPU Acceleration", | ||
263 | "settings.app.form.enableLock": "Enable Ferdi password lock", | ||
264 | "settings.app.form.enableSpellchecking": "Enable spell checking", | ||
265 | "settings.app.form.enableSystemTray": "在系統匣上顯示", | ||
266 | "settings.app.form.enableTodos": "Enable Ferdi Todos", | ||
267 | "settings.app.form.hibernate": "Enable service hibernation", | ||
268 | "settings.app.form.hibernationStrategy": "Hibernation strategy", | ||
269 | "settings.app.form.keepAllWorkspacesLoaded": "Keep all workspaces loaded", | ||
270 | "settings.app.form.language": "語言", | ||
271 | "settings.app.form.lockPassword": "Ferdi Lock password", | ||
272 | "settings.app.form.minimizeToSystemTray": "最小化至系統匣", | ||
273 | "settings.app.form.noUpdates": "Disable updates", | ||
274 | "settings.app.form.privateNotifications": "Don't show message content in notifications", | ||
275 | "settings.app.form.runInBackground": "關閉時保持在背景運作", | ||
276 | "settings.app.form.scheduledDNDEnabled": "Enable scheduled Do-not-Disturb", | ||
277 | "settings.app.form.scheduledDNDEnd": "To", | ||
278 | "settings.app.form.scheduledDNDStart": "From", | ||
279 | "settings.app.form.server": "Server", | ||
280 | "settings.app.form.showDisabledServices": "Display disabled services tabs", | ||
281 | "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled", | ||
282 | "settings.app.form.showServiceNavigationBar": "Always show service navigation bar", | ||
283 | "settings.app.form.todoServer": "Todo Server", | ||
284 | "settings.app.form.universalDarkMode": "Enable universal Dark Mode", | ||
285 | "settings.app.headline": "Settings", | ||
286 | "settings.app.headlineAdvanced": "Advanced", | ||
287 | "settings.app.headlineAppearance": "Appearance", | ||
288 | "settings.app.headlineGeneral": "一般", | ||
289 | "settings.app.headlineLanguage": "語言", | ||
290 | "settings.app.headlineUpdates": "更新", | ||
291 | "settings.app.hibernateInfo": "By default, Ferdi will keep all your services open and loaded in the background so they are ready when you want to use them. Service Hibernation will unload your services after a specified amount. This is useful to save RAM or keeping services from slowing down your computer.", | ||
292 | "settings.app.languageDisclaimer": "Official translations are English & German. All other languages are community based translations.", | ||
293 | "settings.app.lockInfo": "Ferdi password lock allows you to keep your messages protected.\nUsing Ferdi password lock, you will be prompted to enter your password everytime you start Ferdi or lock Ferdi yourself using the lock symbol in the bottom left corner or the shortcut CMD/CTRL+Shift+L.", | ||
294 | "settings.app.lockedPassword": "Ferdi Lock Password", | ||
295 | "settings.app.lockedPasswordInfo": "Please make sure to set a password you'll remember.\nIf you loose this password, you will have to reinstall Ferdi.", | ||
296 | "settings.app.restartRequired": "Changes require restart", | ||
297 | "settings.app.scheduledDNDInfo": "Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.", | ||
298 | "settings.app.scheduledDNDTimeInfo": "Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.", | ||
299 | "settings.app.serverInfo": "We advice you to logout after changing your server as your settings might not be saved otherwise.", | ||
300 | "settings.app.serverMoneyInfo": "You are using the official Franz Server for Ferdi.\nWe know that Ferdi allows you to use all its features for free but you are still using Franz's server resources - which Franz's creator has to pay for.\nPlease still consider [Link 1]paying for a Franz account[/Link] or [Link 2]using a self-hosted ferdi-server[/Link] (if you have the knowledge and resources to do so). \nBy using Ferdi, you still profit greatly from Franz's recipe store, server resources and its development.", | ||
301 | "settings.app.subheadlineCache": "Cache", | ||
302 | "settings.app.todoServerInfo": "This server will be used for the \"Ferdi Todo\" feature. (default: https://app.franztodos.com)", | ||
303 | "settings.app.translationHelp": "Help us to translate Ferdi into your language.", | ||
304 | "settings.app.universalDarkModeInfo": "Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.", | ||
305 | "settings.app.updateStatusAvailable": "有可用更新,下載中...", | ||
306 | "settings.app.updateStatusSearching": "檢查更新中...", | ||
307 | "settings.app.updateStatusUpToDate": "已經是最新版本了", | ||
308 | "settings.invite.headline": "Invite Friends", | ||
309 | "settings.navigation.account": "帳戶", | ||
310 | "settings.navigation.availableServices": "可用服務", | ||
311 | "settings.navigation.logout": "登出", | ||
312 | "settings.navigation.settings": "Settings", | ||
313 | "settings.navigation.supportFerdi": "Support Ferdi", | ||
314 | "settings.navigation.team": "Manage Team", | ||
315 | "settings.navigation.yourServices": "您的服務", | ||
316 | "settings.navigation.yourWorkspaces": "Your workspaces", | ||
317 | "settings.recipes.all": "All services", | ||
318 | "settings.recipes.custom": "Custom Services", | ||
319 | "settings.recipes.customService.headline.communityRecipes": "Community 3rd Party Recipes", | ||
320 | "settings.recipes.customService.headline.customRecipes": "Custom 3rd Party Recipes", | ||
321 | "settings.recipes.customService.headline.devRecipes": "Your Development Service Recipes", | ||
322 | "settings.recipes.customService.intro": "To add a custom service, copy the service recipe to:", | ||
323 | "settings.recipes.customService.openDevDocs": "Developer Documentation", | ||
324 | "settings.recipes.customService.openFolder": "Open folder", | ||
325 | "settings.recipes.headline": "可用服務", | ||
326 | "settings.recipes.missingService": "Missing a service?", | ||
327 | "settings.recipes.mostPopular": "熱門", | ||
328 | "settings.recipes.nothingFound": "抱歉,找不到您所要的服務", | ||
329 | "settings.recipes.servicesSuccessfulAddedInfo": "新增服務成功", | ||
330 | "settings.searchService": "Search service", | ||
331 | "settings.service.error.goBack": "返回", | ||
332 | "settings.service.error.headline": "Error", | ||
333 | "settings.service.error.message": "無法載入服務元件", | ||
334 | "settings.service.form.addServiceHeadline": "新增 {name}", | ||
335 | "settings.service.form.availableServices": "可用服務", | ||
336 | "settings.service.form.customUrl": "Custom server", | ||
337 | "settings.service.form.customUrlPremiumInfo": "To add self hosted services, you need a Ferdi Premium Supporter Account.", | ||
338 | "settings.service.form.customUrlUpgradeAccount": "升級帳戶", | ||
339 | "settings.service.form.customUrlValidationError": "Could not validate custom {name} server.", | ||
340 | "settings.service.form.deleteButton": "刪除", | ||
341 | "settings.service.form.editServiceHeadline": "Edit {name}", | ||
342 | "settings.service.form.enableAudio": "Enable audio", | ||
343 | "settings.service.form.enableBadge": "Show unread message badges", | ||
344 | "settings.service.form.enableDarkMode": "Enable Dark Mode", | ||
345 | "settings.service.form.enableNotification": "啟用通知", | ||
346 | "settings.service.form.enableService": "啟用服務", | ||
347 | "settings.service.form.headlineBadges": "Unread message badges", | ||
348 | "settings.service.form.headlineGeneral": "一般", | ||
349 | "settings.service.form.headlineNotifications": "Notifications", | ||
350 | "settings.service.form.icon": "Custom icon", | ||
351 | "settings.service.form.iconDelete": "Delete", | ||
352 | "settings.service.form.iconUpload": "Drop your image, or click here", | ||
353 | "settings.service.form.indirectMessageInfo": "除了 @username, @channel, @here 之外,當您參與的頻道有訊息時,就會通知", | ||
354 | "settings.service.form.indirectMessages": "針對全部訊息顯示通知", | ||
355 | "settings.service.form.isMutedInfo": "When disabled, all notification sounds and audio playback are muted", | ||
356 | "settings.service.form.name": "名子", | ||
357 | "settings.service.form.openDarkmodeCss": "Open darkmode.css", | ||
358 | "settings.service.form.proxy.headline": "HTTP/HTTPS Proxy Settings", | ||
359 | "settings.service.form.proxy.host": "Proxy Host/IP", | ||
360 | "settings.service.form.proxy.info": "Proxy settings will not synced with the Ferdi servers.", | ||
361 | "settings.service.form.proxy.isEnabled": "Use Proxy", | ||
362 | "settings.service.form.proxy.password": "Password (optional)", | ||
363 | "settings.service.form.proxy.port": "Port", | ||
364 | "settings.service.form.proxy.restartInfo": "Please restart Ferdi after changing proxy Settings.", | ||
365 | "settings.service.form.proxy.user": "User (optional)", | ||
366 | "settings.service.form.saveButton": "儲存", | ||
367 | "settings.service.form.tabHosted": "Hosted", | ||
368 | "settings.service.form.tabOnPremise": "Self hosted ⭐️", | ||
369 | "settings.service.form.team": "Team", | ||
370 | "settings.service.form.useHostedService": "Use the hosted {name} service.", | ||
371 | "settings.service.form.yourServices": "您的服務", | ||
372 | "settings.services.deletedInfo": "服務已刪除", | ||
373 | "settings.services.discoverServices": "服務列表", | ||
374 | "settings.services.headline": "您的服務", | ||
375 | "settings.services.noServicesAdded": "您還沒加入任何服務", | ||
376 | "settings.services.servicesRequestFailed": "Could not load your services", | ||
377 | "settings.services.tooltip.isDisabled": "已停用服務", | ||
378 | "settings.services.tooltip.isMuted": "All sounds are muted", | ||
379 | "settings.services.tooltip.notificationsDisabled": "已停用通知", | ||
380 | "settings.services.updatedInfo": "您的更改已經儲存", | ||
381 | "settings.supportFerdi.github": "Star on GitHub", | ||
382 | "settings.supportFerdi.headline": "Support Ferdi", | ||
383 | "settings.supportFerdi.openCollective": "Support our Open Collective", | ||
384 | "settings.supportFerdi.share": "Tell your Friends", | ||
385 | "settings.supportFerdi.title": "Do you like Ferdi? Spread the love!", | ||
386 | "settings.team.contentHeadline": "Ferdi for Teams", | ||
387 | "settings.team.copy": "Ferdi for Teams gives you the option to invite co-workers to your team by sending them email invitations and manage their subscriptions in your account’s preferences. Don’t waste time setting up subscriptions for every team member individually, forget about multiple invoices and different billing cycles - one team to rule them all!", | ||
388 | "settings.team.headline": "Team", | ||
389 | "settings.team.intro": "You and your team use Ferdi? You can now manage Premium subscriptions for as many colleagues, friends or family members as you want, all from within one account.", | ||
390 | "settings.team.manageAction": "Manage your Team on getferdi.com", | ||
391 | "settings.team.teamsUnavailable": "Teams are unavailable", | ||
392 | "settings.team.teamsUnavailableInfo": "Teams are currently only available when using the Franz Server and after paying for Franz Professional. Please change your server to https://api.franzinfra.com to use teams.", | ||
393 | "settings.team.upgradeAction": "Upgrade your Account", | ||
394 | "settings.user.form.accountType.company": "公司", | ||
395 | "settings.user.form.accountType.individual": "個人", | ||
396 | "settings.user.form.accountType.label": "帳戶類型", | ||
397 | "settings.user.form.accountType.non-profit": "非營利", | ||
398 | "settings.user.form.currentPassword": "舊密碼", | ||
399 | "settings.user.form.email": "電子郵件信箱", | ||
400 | "settings.user.form.firstname": "名子", | ||
401 | "settings.user.form.lastname": "姓氏", | ||
402 | "settings.user.form.newPassword": "新密碼", | ||
403 | "settings.workspace.add.form.name": "名子", | ||
404 | "settings.workspace.add.form.submitButton": "Create workspace", | ||
405 | "settings.workspace.form.buttonDelete": "Delete workspace", | ||
406 | "settings.workspace.form.buttonSave": "Save workspace", | ||
407 | "settings.workspace.form.keepLoaded": "Keep this workspace loaded*", | ||
408 | "settings.workspace.form.keepLoadedInfo": "*This option will be overwritten by the global \"Keep all workspaces loaded\" option.", | ||
409 | "settings.workspace.form.name": "名子", | ||
410 | "settings.workspace.form.servicesInWorkspaceHeadline": "Services in this Workspace", | ||
411 | "settings.workspace.form.yourWorkspaces": "Your workspaces", | ||
412 | "settings.workspaces.deletedInfo": "Workspace has been deleted", | ||
413 | "settings.workspaces.headline": "Your workspaces", | ||
414 | "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.", | ||
415 | "settings.workspaces.tryReloadWorkspaces": "Try again", | ||
416 | "settings.workspaces.updatedInfo": "您的更改已經儲存", | ||
417 | "settings.workspaces.workspaceFeatureHeadline": "Less is More: Introducing Ferdi Workspaces", | ||
418 | "settings.workspaces.workspaceFeatureInfo": "Ferdi Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time. You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.", | ||
419 | "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces", | ||
420 | "sidebar.addNewService": "Add new service", | ||
421 | "sidebar.closeTodosDrawer": "Close Ferdi Todos", | ||
422 | "sidebar.closeWorkspaceDrawer": "Close workspace drawer", | ||
423 | "sidebar.lockFerdi": "Lock Ferdi", | ||
424 | "sidebar.muteApp": "Disable notifications & audio", | ||
425 | "sidebar.openTodosDrawer": "Open Ferdi Todos", | ||
426 | "sidebar.openWorkspaceDrawer": "Open workspace drawer", | ||
427 | "sidebar.settings": "Settings", | ||
428 | "sidebar.unmuteApp": "Enable notifications & audio", | ||
429 | "signup.email.label": "電子郵件信箱", | ||
430 | "signup.emailDuplicate": "此電子郵件信箱已被註冊", | ||
431 | "signup.firstname.label": "名子", | ||
432 | "signup.headline": "註冊", | ||
433 | "signup.lastname.label": "姓氏", | ||
434 | "signup.legal.info": "在建立帳戶同時,您同意:", | ||
435 | "signup.legal.privacy": "Privacy Statement", | ||
436 | "signup.legal.terms": "服務條款", | ||
437 | "signup.link.login": "您已有一個帳戶,請問是否要登入?", | ||
438 | "signup.password.label": "Password", | ||
439 | "signup.submit.label": "建立帳戶", | ||
440 | "subscription.bestValue": "Best value", | ||
441 | "subscription.cta.activateTrial": "Yes, start the free Ferdi Professional trial", | ||
442 | "subscription.cta.allOptions": "See all options", | ||
443 | "subscription.cta.choosePlan": "Choose your plan", | ||
444 | "subscription.includedProFeatures": "The Ferdi Professional Plan includes:", | ||
445 | "subscription.interval.per": "per {interval}", | ||
446 | "subscription.interval.perMonth": "per month", | ||
447 | "subscription.interval.perMonthPerUser": "per month & user", | ||
448 | "subscription.planItem.upgradeAccount": "Upgrade Account", | ||
449 | "subscription.teaser.includedFeatures": "Paid Ferdi Plans include:", | ||
450 | "subscription.teaser.intro": "Ferdi 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!", | ||
451 | "subscriptionPopup.buttonCancel": "取消", | ||
452 | "subscriptionPopup.buttonDone": "完成", | ||
453 | "tabs.item.deleteService": "刪除", | ||
454 | "tabs.item.disableAudio": "Disable audio", | ||
455 | "tabs.item.disableNotifications": "停用通知", | ||
456 | "tabs.item.disableService": "停用服務", | ||
457 | "tabs.item.edit": "Edit", | ||
458 | "tabs.item.enableAudio": "Enable audio", | ||
459 | "tabs.item.enableNotification": "啟用通知", | ||
460 | "tabs.item.enableService": "啟用服務", | ||
461 | "tabs.item.reload": "Reload", | ||
462 | "validation.email": "{field} is not valid", | ||
463 | "validation.minLength": "{field} should be at least {length} characters long", | ||
464 | "validation.oneRequired": "At least one is required", | ||
465 | "validation.required": "{field} is required", | ||
466 | "validation.url": "{field} is not a valid URL", | ||
467 | "webControls.back": "Back", | ||
468 | "webControls.forward": "Forward", | ||
469 | "webControls.goHome": "Home", | ||
470 | "webControls.openInBrowser": "Open in Browser", | ||
471 | "webControls.reload": "Reload", | ||
472 | "welcome.loginButton": "登入", | ||
473 | "welcome.signupButton": "建立一個免費帳戶", | ||
474 | "workspaceDrawer.addNewWorkspaceLabel": "Add new workspace", | ||
475 | "workspaceDrawer.allServices": "All services", | ||
476 | "workspaceDrawer.headline": "Workspaces", | ||
477 | "workspaceDrawer.item.contextMenuEdit": "edit", | ||
478 | "workspaceDrawer.item.noServicesAddedYet": "No services added yet", | ||
479 | "workspaceDrawer.premiumCtaButtonLabel": "Create your first workspace", | ||
480 | "workspaceDrawer.proFeatureBadge": "Premium feature", | ||
481 | "workspaceDrawer.reactivatePremiumAccountLabel": "Reactivate premium account", | ||
482 | "workspaceDrawer.workspaceFeatureInfo": "<p>Ferdi Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.</p><p>You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.</p>", | ||
483 | "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", | ||
484 | "workspaces.switchingIndicator.switchingTo": "Switching to" | ||
485 | } | ||
diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json index 0885f6a20..c4c94bb32 100644 --- a/src/i18n/messages/src/components/auth/Pricing.json +++ b/src/i18n/messages/src/components/auth/Pricing.json | |||
@@ -1,7 +1,7 @@ | |||
1 | [ | 1 | [ |
2 | { | 2 | { |
3 | "id": "pricing.trial.headline", | 3 | "id": "pricing.trial.headline.pro", |
4 | "defaultMessage": "!!!Franz Professional", | 4 | "defaultMessage": "!!!Hi {name}, welcome to Franz", |
5 | "file": "src/components/auth/Pricing.js", | 5 | "file": "src/components/auth/Pricing.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 15, | 7 | "line": 15, |
@@ -13,12 +13,12 @@ | |||
13 | } | 13 | } |
14 | }, | 14 | }, |
15 | { | 15 | { |
16 | "id": "pricing.trial.subheadline", | 16 | "id": "pricing.trial.intro.specialTreat", |
17 | "defaultMessage": "!!!Your personal welcome offer:", | 17 | "defaultMessage": "!!!We have a special treat for you.", |
18 | "file": "src/components/auth/Pricing.js", | 18 | "file": "src/components/auth/Pricing.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 19, | 20 | "line": 19, |
21 | "column": 17 | 21 | "column": 16 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 22, | 24 | "line": 22, |
@@ -26,15 +26,41 @@ | |||
26 | } | 26 | } |
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | "id": "pricing.trial.intro.tryPro", | ||
30 | "defaultMessage": "!!!Enjoy the full Franz Professional experience completely free for 14 days.", | ||
31 | "file": "src/components/auth/Pricing.js", | ||
32 | "start": { | ||
33 | "line": 23, | ||
34 | "column": 10 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 26, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "pricing.trial.intro.happyMessaging", | ||
43 | "defaultMessage": "!!!Happy messaging,", | ||
44 | "file": "src/components/auth/Pricing.js", | ||
45 | "start": { | ||
46 | "line": 27, | ||
47 | "column": 18 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 30, | ||
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
29 | "id": "pricing.trial.terms.headline", | 55 | "id": "pricing.trial.terms.headline", |
30 | "defaultMessage": "!!!No strings attached", | 56 | "defaultMessage": "!!!No strings attached", |
31 | "file": "src/components/auth/Pricing.js", | 57 | "file": "src/components/auth/Pricing.js", |
32 | "start": { | 58 | "start": { |
33 | "line": 23, | 59 | "line": 31, |
34 | "column": 29 | 60 | "column": 29 |
35 | }, | 61 | }, |
36 | "end": { | 62 | "end": { |
37 | "line": 26, | 63 | "line": 34, |
38 | "column": 3 | 64 | "column": 3 |
39 | } | 65 | } |
40 | }, | 66 | }, |
@@ -43,11 +69,11 @@ | |||
43 | "defaultMessage": "!!!No credit card required", | 69 | "defaultMessage": "!!!No credit card required", |
44 | "file": "src/components/auth/Pricing.js", | 70 | "file": "src/components/auth/Pricing.js", |
45 | "start": { | 71 | "start": { |
46 | "line": 27, | 72 | "line": 35, |
47 | "column": 16 | 73 | "column": 16 |
48 | }, | 74 | }, |
49 | "end": { | 75 | "end": { |
50 | "line": 30, | 76 | "line": 38, |
51 | "column": 3 | 77 | "column": 3 |
52 | } | 78 | } |
53 | }, | 79 | }, |
@@ -56,11 +82,24 @@ | |||
56 | "defaultMessage": "!!!Your free trial ends automatically after 14 days", | 82 | "defaultMessage": "!!!Your free trial ends automatically after 14 days", |
57 | "file": "src/components/auth/Pricing.js", | 83 | "file": "src/components/auth/Pricing.js", |
58 | "start": { | 84 | "start": { |
59 | "line": 31, | 85 | "line": 39, |
60 | "column": 21 | 86 | "column": 21 |
61 | }, | 87 | }, |
62 | "end": { | 88 | "end": { |
63 | "line": 34, | 89 | "line": 42, |
90 | "column": 3 | ||
91 | } | ||
92 | }, | ||
93 | { | ||
94 | "id": "pricing.trial.terms.trialWorth", | ||
95 | "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", | ||
96 | "file": "src/components/auth/Pricing.js", | ||
97 | "start": { | ||
98 | "line": 43, | ||
99 | "column": 14 | ||
100 | }, | ||
101 | "end": { | ||
102 | "line": 46, | ||
64 | "column": 3 | 103 | "column": 3 |
65 | } | 104 | } |
66 | }, | 105 | }, |
@@ -69,24 +108,37 @@ | |||
69 | "defaultMessage": "!!!Sorry, we could not activate your trial!", | 108 | "defaultMessage": "!!!Sorry, we could not activate your trial!", |
70 | "file": "src/components/auth/Pricing.js", | 109 | "file": "src/components/auth/Pricing.js", |
71 | "start": { | 110 | "start": { |
72 | "line": 35, | 111 | "line": 47, |
73 | "column": 19 | 112 | "column": 19 |
74 | }, | 113 | }, |
75 | "end": { | 114 | "end": { |
76 | "line": 38, | 115 | "line": 50, |
77 | "column": 3 | 116 | "column": 3 |
78 | } | 117 | } |
79 | }, | 118 | }, |
80 | { | 119 | { |
81 | "id": "pricing.trial.cta.accept", | 120 | "id": "pricing.trial.cta.accept", |
82 | "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", | 121 | "defaultMessage": "!!!Start my 14-day Franz Professional Trial", |
83 | "file": "src/components/auth/Pricing.js", | 122 | "file": "src/components/auth/Pricing.js", |
84 | "start": { | 123 | "start": { |
85 | "line": 39, | 124 | "line": 51, |
86 | "column": 13 | 125 | "column": 13 |
87 | }, | 126 | }, |
88 | "end": { | 127 | "end": { |
89 | "line": 42, | 128 | "line": 54, |
129 | "column": 3 | ||
130 | } | ||
131 | }, | ||
132 | { | ||
133 | "id": "pricing.trial.cta.start", | ||
134 | "defaultMessage": "!!!Start using Franz", | ||
135 | "file": "src/components/auth/Pricing.js", | ||
136 | "start": { | ||
137 | "line": 55, | ||
138 | "column": 12 | ||
139 | }, | ||
140 | "end": { | ||
141 | "line": 58, | ||
90 | "column": 3 | 142 | "column": 3 |
91 | } | 143 | } |
92 | }, | 144 | }, |
@@ -95,11 +147,11 @@ | |||
95 | "defaultMessage": "!!!Continue to Ferdi", | 147 | "defaultMessage": "!!!Continue to Ferdi", |
96 | "file": "src/components/auth/Pricing.js", | 148 | "file": "src/components/auth/Pricing.js", |
97 | "start": { | 149 | "start": { |
98 | "line": 43, | 150 | "line": 59, |
99 | "column": 11 | 151 | "column": 11 |
100 | }, | 152 | }, |
101 | "end": { | 153 | "end": { |
102 | "line": 46, | 154 | "line": 62, |
103 | "column": 3 | 155 | "column": 3 |
104 | } | 156 | } |
105 | }, | 157 | }, |
@@ -108,11 +160,11 @@ | |||
108 | "defaultMessage": "!!!Franz Professional includes:", | 160 | "defaultMessage": "!!!Franz Professional includes:", |
109 | "file": "src/components/auth/Pricing.js", | 161 | "file": "src/components/auth/Pricing.js", |
110 | "start": { | 162 | "start": { |
111 | "line": 47, | 163 | "line": 63, |
112 | "column": 20 | 164 | "column": 20 |
113 | }, | 165 | }, |
114 | "end": { | 166 | "end": { |
115 | "line": 50, | 167 | "line": 66, |
116 | "column": 3 | 168 | "column": 3 |
117 | } | 169 | } |
118 | } | 170 | } |
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json index 0625487b4..bca181d0f 100644 --- a/src/i18n/messages/src/components/layout/AppLayout.json +++ b/src/i18n/messages/src/components/layout/AppLayout.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Your services have been updated.", | 4 | "defaultMessage": "!!!Your services have been updated.", |
5 | "file": "src/components/layout/AppLayout.js", | 5 | "file": "src/components/layout/AppLayout.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 28, | 7 | "line": 30, |
8 | "column": 19 | 8 | "column": 19 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 31, | 11 | "line": 33, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Reload services", | 17 | "defaultMessage": "!!!Reload services", |
18 | "file": "src/components/layout/AppLayout.js", | 18 | "file": "src/components/layout/AppLayout.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 32, | 20 | "line": 34, |
21 | "column": 24 | 21 | "column": 24 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 35, | 24 | "line": 37, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,11 @@ | |||
30 | "defaultMessage": "!!!Could not load services and user information", | 30 | "defaultMessage": "!!!Could not load services and user information", |
31 | "file": "src/components/layout/AppLayout.js", | 31 | "file": "src/components/layout/AppLayout.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 36, | 33 | "line": 38, |
34 | "column": 26 | 34 | "column": 26 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 39, | 37 | "line": 41, |
38 | "column": 3 | 38 | "column": 3 |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,11 +43,11 @@ | |||
43 | "defaultMessage": "!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.", | 43 | "defaultMessage": "!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.", |
44 | "file": "src/components/layout/AppLayout.js", | 44 | "file": "src/components/layout/AppLayout.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 40, | 46 | "line": 42, |
47 | "column": 21 | 47 | "column": 21 |
48 | }, | 48 | }, |
49 | "end": { | 49 | "end": { |
50 | "line": 43, | 50 | "line": 45, |
51 | "column": 3 | 51 | "column": 3 |
52 | } | 52 | } |
53 | } | 53 | } |
diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json index 497e299a4..3201115b3 100644 --- a/src/i18n/messages/src/components/ui/FeatureList.json +++ b/src/i18n/messages/src/components/ui/FeatureList.json | |||
@@ -1,14 +1,79 @@ | |||
1 | [ | 1 | [ |
2 | { | 2 | { |
3 | "id": "pricing.features.recipes", | ||
4 | "defaultMessage": "!!!Choose from more than 70 Services", | ||
5 | "file": "src/components/ui/FeatureList.js", | ||
6 | "start": { | ||
7 | "line": 9, | ||
8 | "column": 20 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 12, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "pricing.features.accountSync", | ||
17 | "defaultMessage": "!!!Account Synchronisation", | ||
18 | "file": "src/components/ui/FeatureList.js", | ||
19 | "start": { | ||
20 | "line": 13, | ||
21 | "column": 15 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 16, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "pricing.features.desktopNotifications", | ||
30 | "defaultMessage": "!!!Desktop Notifications", | ||
31 | "file": "src/components/ui/FeatureList.js", | ||
32 | "start": { | ||
33 | "line": 17, | ||
34 | "column": 24 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 20, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
3 | "id": "pricing.features.unlimitedServices", | 42 | "id": "pricing.features.unlimitedServices", |
4 | "defaultMessage": "!!!Add unlimited services", | 43 | "defaultMessage": "!!!Add unlimited services", |
5 | "file": "src/components/ui/FeatureList.js", | 44 | "file": "src/components/ui/FeatureList.js", |
6 | "start": { | 45 | "start": { |
7 | "line": 8, | 46 | "line": 21, |
8 | "column": 21 | 47 | "column": 21 |
9 | }, | 48 | }, |
10 | "end": { | 49 | "end": { |
11 | "line": 11, | 50 | "line": 24, |
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | "id": "pricing.features.upToThreeServices", | ||
56 | "defaultMessage": "!!!Add up to 3 services", | ||
57 | "file": "src/components/ui/FeatureList.js", | ||
58 | "start": { | ||
59 | "line": 25, | ||
60 | "column": 21 | ||
61 | }, | ||
62 | "end": { | ||
63 | "line": 28, | ||
64 | "column": 3 | ||
65 | } | ||
66 | }, | ||
67 | { | ||
68 | "id": "pricing.features.upToSixServices", | ||
69 | "defaultMessage": "!!!Add up to 6 services", | ||
70 | "file": "src/components/ui/FeatureList.js", | ||
71 | "start": { | ||
72 | "line": 29, | ||
73 | "column": 19 | ||
74 | }, | ||
75 | "end": { | ||
76 | "line": 32, | ||
12 | "column": 3 | 77 | "column": 3 |
13 | } | 78 | } |
14 | }, | 79 | }, |
@@ -17,11 +82,11 @@ | |||
17 | "defaultMessage": "!!!Spellchecker support", | 82 | "defaultMessage": "!!!Spellchecker support", |
18 | "file": "src/components/ui/FeatureList.js", | 83 | "file": "src/components/ui/FeatureList.js", |
19 | "start": { | 84 | "start": { |
20 | "line": 12, | 85 | "line": 33, |
21 | "column": 16 | 86 | "column": 16 |
22 | }, | 87 | }, |
23 | "end": { | 88 | "end": { |
24 | "line": 15, | 89 | "line": 36, |
25 | "column": 3 | 90 | "column": 3 |
26 | } | 91 | } |
27 | }, | 92 | }, |
@@ -30,11 +95,11 @@ | |||
30 | "defaultMessage": "!!!Workspaces", | 95 | "defaultMessage": "!!!Workspaces", |
31 | "file": "src/components/ui/FeatureList.js", | 96 | "file": "src/components/ui/FeatureList.js", |
32 | "start": { | 97 | "start": { |
33 | "line": 16, | 98 | "line": 37, |
34 | "column": 14 | 99 | "column": 14 |
35 | }, | 100 | }, |
36 | "end": { | 101 | "end": { |
37 | "line": 19, | 102 | "line": 40, |
38 | "column": 3 | 103 | "column": 3 |
39 | } | 104 | } |
40 | }, | 105 | }, |
@@ -43,11 +108,11 @@ | |||
43 | "defaultMessage": "!!!Add Custom Websites", | 108 | "defaultMessage": "!!!Add Custom Websites", |
44 | "file": "src/components/ui/FeatureList.js", | 109 | "file": "src/components/ui/FeatureList.js", |
45 | "start": { | 110 | "start": { |
46 | "line": 20, | 111 | "line": 41, |
47 | "column": 18 | 112 | "column": 18 |
48 | }, | 113 | }, |
49 | "end": { | 114 | "end": { |
50 | "line": 23, | 115 | "line": 44, |
51 | "column": 3 | 116 | "column": 3 |
52 | } | 117 | } |
53 | }, | 118 | }, |
@@ -56,11 +121,11 @@ | |||
56 | "defaultMessage": "!!!On-premise & other Hosted Services", | 121 | "defaultMessage": "!!!On-premise & other Hosted Services", |
57 | "file": "src/components/ui/FeatureList.js", | 122 | "file": "src/components/ui/FeatureList.js", |
58 | "start": { | 123 | "start": { |
59 | "line": 24, | 124 | "line": 45, |
60 | "column": 13 | 125 | "column": 13 |
61 | }, | 126 | }, |
62 | "end": { | 127 | "end": { |
63 | "line": 27, | 128 | "line": 48, |
64 | "column": 3 | 129 | "column": 3 |
65 | } | 130 | } |
66 | }, | 131 | }, |
@@ -69,11 +134,11 @@ | |||
69 | "defaultMessage": "!!!Install 3rd party services", | 134 | "defaultMessage": "!!!Install 3rd party services", |
70 | "file": "src/components/ui/FeatureList.js", | 135 | "file": "src/components/ui/FeatureList.js", |
71 | "start": { | 136 | "start": { |
72 | "line": 28, | 137 | "line": 49, |
73 | "column": 22 | 138 | "column": 22 |
74 | }, | 139 | }, |
75 | "end": { | 140 | "end": { |
76 | "line": 31, | 141 | "line": 52, |
77 | "column": 3 | 142 | "column": 3 |
78 | } | 143 | } |
79 | }, | 144 | }, |
@@ -82,11 +147,11 @@ | |||
82 | "defaultMessage": "!!!Service Proxies", | 147 | "defaultMessage": "!!!Service Proxies", |
83 | "file": "src/components/ui/FeatureList.js", | 148 | "file": "src/components/ui/FeatureList.js", |
84 | "start": { | 149 | "start": { |
85 | "line": 32, | 150 | "line": 53, |
86 | "column": 18 | 151 | "column": 18 |
87 | }, | 152 | }, |
88 | "end": { | 153 | "end": { |
89 | "line": 35, | 154 | "line": 56, |
90 | "column": 3 | 155 | "column": 3 |
91 | } | 156 | } |
92 | }, | 157 | }, |
@@ -95,11 +160,11 @@ | |||
95 | "defaultMessage": "!!!Team Management", | 160 | "defaultMessage": "!!!Team Management", |
96 | "file": "src/components/ui/FeatureList.js", | 161 | "file": "src/components/ui/FeatureList.js", |
97 | "start": { | 162 | "start": { |
98 | "line": 36, | 163 | "line": 57, |
99 | "column": 18 | 164 | "column": 18 |
100 | }, | 165 | }, |
101 | "end": { | 166 | "end": { |
102 | "line": 39, | 167 | "line": 60, |
103 | "column": 3 | 168 | "column": 3 |
104 | } | 169 | } |
105 | }, | 170 | }, |
@@ -108,11 +173,11 @@ | |||
108 | "defaultMessage": "!!!No Waiting Screens", | 173 | "defaultMessage": "!!!No Waiting Screens", |
109 | "file": "src/components/ui/FeatureList.js", | 174 | "file": "src/components/ui/FeatureList.js", |
110 | "start": { | 175 | "start": { |
111 | "line": 40, | 176 | "line": 61, |
112 | "column": 13 | 177 | "column": 13 |
113 | }, | 178 | }, |
114 | "end": { | 179 | "end": { |
115 | "line": 43, | 180 | "line": 64, |
116 | "column": 3 | 181 | "column": 3 |
117 | } | 182 | } |
118 | }, | 183 | }, |
@@ -121,11 +186,11 @@ | |||
121 | "defaultMessage": "!!!Forever ad-free", | 186 | "defaultMessage": "!!!Forever ad-free", |
122 | "file": "src/components/ui/FeatureList.js", | 187 | "file": "src/components/ui/FeatureList.js", |
123 | "start": { | 188 | "start": { |
124 | "line": 44, | 189 | "line": 65, |
125 | "column": 10 | 190 | "column": 10 |
126 | }, | 191 | }, |
127 | "end": { | 192 | "end": { |
128 | "line": 47, | 193 | "line": 68, |
129 | "column": 3 | 194 | "column": 3 |
130 | } | 195 | } |
131 | } | 196 | } |
diff --git a/src/i18n/messages/src/features/delayApp/Component.json b/src/i18n/messages/src/features/delayApp/Component.json index 77fabf236..f1d6886f5 100644 --- a/src/i18n/messages/src/features/delayApp/Component.json +++ b/src/i18n/messages/src/features/delayApp/Component.json | |||
@@ -27,7 +27,7 @@ | |||
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | "id": "feature.delayApp.upgrade.action", | 29 | "id": "feature.delayApp.upgrade.action", |
30 | "defaultMessage": "!!!Get a Franz Supporter License", | 30 | "defaultMessage": "!!!Upgrade Franz", |
31 | "file": "src/features/delayApp/Component.js", | 31 | "file": "src/features/delayApp/Component.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 22, | 33 | "line": 22, |
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanItem.json b/src/i18n/messages/src/features/planSelection/components/PlanItem.json new file mode 100644 index 000000000..5a94f32ee --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanItem.json | |||
@@ -0,0 +1,41 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "subscription.interval.perMonth", | ||
4 | "defaultMessage": "!!!per month", | ||
5 | "file": "src/features/planSelection/components/PlanItem.js", | ||
6 | "start": { | ||
7 | "line": 15, | ||
8 | "column": 12 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 18, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "subscription.interval.perMonthPerUser", | ||
17 | "defaultMessage": "!!!per month & user", | ||
18 | "file": "src/features/planSelection/components/PlanItem.js", | ||
19 | "start": { | ||
20 | "line": 19, | ||
21 | "column": 19 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 22, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "subscription.bestValue", | ||
30 | "defaultMessage": "!!!Best value", | ||
31 | "file": "src/features/planSelection/components/PlanItem.js", | ||
32 | "start": { | ||
33 | "line": 23, | ||
34 | "column": 13 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 26, | ||
38 | "column": 3 | ||
39 | } | ||
40 | } | ||
41 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanSelection.json b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json new file mode 100644 index 000000000..ed354146e --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json | |||
@@ -0,0 +1,158 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "feature.planSelection.fullscreen.welcome", | ||
4 | "defaultMessage": "!!!Are you ready to choose, {name}", | ||
5 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
6 | "start": { | ||
7 | "line": 17, | ||
8 | "column": 11 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 20, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "feature.planSelection.fullscreen.subheadline", | ||
17 | "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", | ||
18 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
19 | "start": { | ||
20 | "line": 21, | ||
21 | "column": 15 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 24, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "feature.planSelection.free.text", | ||
30 | "defaultMessage": "!!!Basic functionality", | ||
31 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
32 | "start": { | ||
33 | "line": 25, | ||
34 | "column": 12 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 28, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "feature.planSelection.personal.text", | ||
43 | "defaultMessage": "!!!More services, no waiting - ideal for personal use.", | ||
44 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
45 | "start": { | ||
46 | "line": 29, | ||
47 | "column": 16 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 32, | ||
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | "id": "feature.planSelection.pro.text", | ||
56 | "defaultMessage": "!!!Unlimited services and professional features for you - and your team.", | ||
57 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
58 | "start": { | ||
59 | "line": 33, | ||
60 | "column": 20 | ||
61 | }, | ||
62 | "end": { | ||
63 | "line": 36, | ||
64 | "column": 3 | ||
65 | } | ||
66 | }, | ||
67 | { | ||
68 | "id": "feature.planSelection.cta.stayOnFree", | ||
69 | "defaultMessage": "!!!Stay on Free", | ||
70 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
71 | "start": { | ||
72 | "line": 37, | ||
73 | "column": 17 | ||
74 | }, | ||
75 | "end": { | ||
76 | "line": 40, | ||
77 | "column": 3 | ||
78 | } | ||
79 | }, | ||
80 | { | ||
81 | "id": "feature.planSelection.cta.ctaDowngradeFree", | ||
82 | "defaultMessage": "!!!Downgrade to Free", | ||
83 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
84 | "start": { | ||
85 | "line": 41, | ||
86 | "column": 20 | ||
87 | }, | ||
88 | "end": { | ||
89 | "line": 44, | ||
90 | "column": 3 | ||
91 | } | ||
92 | }, | ||
93 | { | ||
94 | "id": "feature.planSelection.cta.trial", | ||
95 | "defaultMessage": "!!!Start my free 14-days Trial", | ||
96 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
97 | "start": { | ||
98 | "line": 45, | ||
99 | "column": 15 | ||
100 | }, | ||
101 | "end": { | ||
102 | "line": 48, | ||
103 | "column": 3 | ||
104 | } | ||
105 | }, | ||
106 | { | ||
107 | "id": "feature.planSelection.cta.upgradePersonal", | ||
108 | "defaultMessage": "!!!Choose Personal", | ||
109 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
110 | "start": { | ||
111 | "line": 49, | ||
112 | "column": 23 | ||
113 | }, | ||
114 | "end": { | ||
115 | "line": 52, | ||
116 | "column": 3 | ||
117 | } | ||
118 | }, | ||
119 | { | ||
120 | "id": "feature.planSelection.cta.upgradePro", | ||
121 | "defaultMessage": "!!!Choose Professional", | ||
122 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
123 | "start": { | ||
124 | "line": 53, | ||
125 | "column": 18 | ||
126 | }, | ||
127 | "end": { | ||
128 | "line": 56, | ||
129 | "column": 3 | ||
130 | } | ||
131 | }, | ||
132 | { | ||
133 | "id": "feature.planSelection.fullFeatureList", | ||
134 | "defaultMessage": "!!!Complete comparison of all plans", | ||
135 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
136 | "start": { | ||
137 | "line": 57, | ||
138 | "column": 19 | ||
139 | }, | ||
140 | "end": { | ||
141 | "line": 60, | ||
142 | "column": 3 | ||
143 | } | ||
144 | }, | ||
145 | { | ||
146 | "id": "feature.planSelection.pricesBasedOnAnnualPayment", | ||
147 | "defaultMessage": "!!!All prices based on yearly payment", | ||
148 | "file": "src/features/planSelection/components/PlanSelection.js", | ||
149 | "start": { | ||
150 | "line": 61, | ||
151 | "column": 30 | ||
152 | }, | ||
153 | "end": { | ||
154 | "line": 64, | ||
155 | "column": 3 | ||
156 | } | ||
157 | } | ||
158 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json new file mode 100644 index 000000000..015304a2e --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json | |||
@@ -0,0 +1,28 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "subscription.interval.per", | ||
4 | "defaultMessage": "!!!per {interval}", | ||
5 | "file": "src/features/planSelection/components/PlanTeaser.js", | ||
6 | "start": { | ||
7 | "line": 16, | ||
8 | "column": 7 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 19, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "subscription.planItem.upgradeAccount", | ||
17 | "defaultMessage": "!!!Upgrade Account", | ||
18 | "file": "src/features/planSelection/components/PlanTeaser.js", | ||
19 | "start": { | ||
20 | "line": 20, | ||
21 | "column": 7 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 23, | ||
25 | "column": 3 | ||
26 | } | ||
27 | } | ||
28 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json new file mode 100644 index 000000000..04b2144b4 --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json | |||
@@ -0,0 +1,54 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "feature.planSelection.fullscreen.dialog.title", | ||
4 | "defaultMessage": "!!!Downgrade your Franz Plan", | ||
5 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
6 | "start": { | ||
7 | "line": 16, | ||
8 | "column": 15 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 19, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "feature.planSelection.fullscreen.dialog.message", | ||
17 | "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
18 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
19 | "start": { | ||
20 | "line": 20, | ||
21 | "column": 17 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 23, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "feature.planSelection.fullscreen.dialog.cta.downgrade", | ||
30 | "defaultMessage": "!!!Downgrade to Free", | ||
31 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
32 | "start": { | ||
33 | "line": 24, | ||
34 | "column": 22 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 27, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "feature.planSelection.fullscreen.dialog.cta.upgrade", | ||
43 | "defaultMessage": "!!!Choose Personal", | ||
44 | "file": "src/features/planSelection/containers/PlanSelectionScreen.js", | ||
45 | "start": { | ||
46 | "line": 28, | ||
47 | "column": 20 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 31, | ||
51 | "column": 3 | ||
52 | } | ||
53 | } | ||
54 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json b/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json new file mode 100644 index 000000000..bf211a016 --- /dev/null +++ b/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json | |||
@@ -0,0 +1,41 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "feature.trialStatusBar.restTime", | ||
4 | "defaultMessage": "!!!Your Free Franz {plan} Trial ends in {time}.", | ||
5 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
6 | "start": { | ||
7 | "line": 13, | ||
8 | "column": 12 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 16, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "feature.trialStatusBar.expired", | ||
17 | "defaultMessage": "!!!Your free Franz {plan} Trial has expired, please upgrade your account.", | ||
18 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
19 | "start": { | ||
20 | "line": 17, | ||
21 | "column": 11 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 20, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "feature.trialStatusBar.cta", | ||
30 | "defaultMessage": "!!!Upgrade now", | ||
31 | "file": "src/features/trialStatusBar/components/TrialStatusBar.js", | ||
32 | "start": { | ||
33 | "line": 21, | ||
34 | "column": 7 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 24, | ||
38 | "column": 3 | ||
39 | } | ||
40 | } | ||
41 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json b/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json new file mode 100644 index 000000000..306cd0fee --- /dev/null +++ b/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json | |||
@@ -0,0 +1,54 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "feature.trialStatusBar.fullscreen.dialog.title", | ||
4 | "defaultMessage": "!!!Downgrade your Franz Plan", | ||
5 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
6 | "start": { | ||
7 | "line": 16, | ||
8 | "column": 15 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 19, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "feature.trialStatusBar.fullscreen.dialog.message", | ||
17 | "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", | ||
18 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
19 | "start": { | ||
20 | "line": 20, | ||
21 | "column": 17 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 23, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "feature.trialStatusBar.fullscreen.dialog.cta.downgrade", | ||
30 | "defaultMessage": "!!!Downgrade to Free", | ||
31 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
32 | "start": { | ||
33 | "line": 24, | ||
34 | "column": 22 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 27, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "feature.trialStatusBar.fullscreen.dialog.cta.upgrade", | ||
43 | "defaultMessage": "!!!Choose Personal", | ||
44 | "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", | ||
45 | "start": { | ||
46 | "line": 28, | ||
47 | "column": 20 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 31, | ||
51 | "column": 3 | ||
52 | } | ||
53 | } | ||
54 | ] \ No newline at end of file | ||
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json index 4111ba760..87b8942ce 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Your workspaces", | 4 | "defaultMessage": "!!!Your workspaces", |
5 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 5 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 19, | 7 | "line": 20, |
8 | "column": 12 | 8 | "column": 12 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 22, | 11 | "line": 23, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!You haven't added any workspaces yet.", | 17 | "defaultMessage": "!!!You haven't added any workspaces yet.", |
18 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 18 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 23, | 20 | "line": 24, |
21 | "column": 19 | 21 | "column": 19 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 26, | 24 | "line": 27, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
@@ -30,11 +30,11 @@ | |||
30 | "defaultMessage": "!!!Could not load your workspaces", | 30 | "defaultMessage": "!!!Could not load your workspaces", |
31 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 31 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 27, | 33 | "line": 28, |
34 | "column": 27 | 34 | "column": 27 |
35 | }, | 35 | }, |
36 | "end": { | 36 | "end": { |
37 | "line": 30, | 37 | "line": 31, |
38 | "column": 3 | 38 | "column": 3 |
39 | } | 39 | } |
40 | }, | 40 | }, |
@@ -43,11 +43,11 @@ | |||
43 | "defaultMessage": "!!!Try again", | 43 | "defaultMessage": "!!!Try again", |
44 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 44 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 31, | 46 | "line": 32, |
47 | "column": 23 | 47 | "column": 23 |
48 | }, | 48 | }, |
49 | "end": { | 49 | "end": { |
50 | "line": 34, | 50 | "line": 35, |
51 | "column": 3 | 51 | "column": 3 |
52 | } | 52 | } |
53 | }, | 53 | }, |
@@ -56,11 +56,11 @@ | |||
56 | "defaultMessage": "!!!Your changes have been saved", | 56 | "defaultMessage": "!!!Your changes have been saved", |
57 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 57 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
58 | "start": { | 58 | "start": { |
59 | "line": 35, | 59 | "line": 36, |
60 | "column": 15 | 60 | "column": 15 |
61 | }, | 61 | }, |
62 | "end": { | 62 | "end": { |
63 | "line": 38, | 63 | "line": 39, |
64 | "column": 3 | 64 | "column": 3 |
65 | } | 65 | } |
66 | }, | 66 | }, |
@@ -69,11 +69,11 @@ | |||
69 | "defaultMessage": "!!!Workspace has been deleted", | 69 | "defaultMessage": "!!!Workspace has been deleted", |
70 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 70 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
71 | "start": { | 71 | "start": { |
72 | "line": 39, | 72 | "line": 40, |
73 | "column": 15 | 73 | "column": 15 |
74 | }, | 74 | }, |
75 | "end": { | 75 | "end": { |
76 | "line": 42, | 76 | "line": 43, |
77 | "column": 3 | 77 | "column": 3 |
78 | } | 78 | } |
79 | }, | 79 | }, |
@@ -82,11 +82,11 @@ | |||
82 | "defaultMessage": "!!!Info about workspace feature", | 82 | "defaultMessage": "!!!Info about workspace feature", |
83 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 83 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
84 | "start": { | 84 | "start": { |
85 | "line": 43, | 85 | "line": 44, |
86 | "column": 24 | 86 | "column": 24 |
87 | }, | 87 | }, |
88 | "end": { | 88 | "end": { |
89 | "line": 46, | 89 | "line": 47, |
90 | "column": 3 | 90 | "column": 3 |
91 | } | 91 | } |
92 | }, | 92 | }, |
@@ -95,11 +95,11 @@ | |||
95 | "defaultMessage": "!!!Less is More: Introducing Ferdi Workspaces", | 95 | "defaultMessage": "!!!Less is More: Introducing Ferdi Workspaces", |
96 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", | 96 | "file": "src/features/workspaces/components/WorkspacesDashboard.js", |
97 | "start": { | 97 | "start": { |
98 | "line": 47, | 98 | "line": 48, |
99 | "column": 28 | 99 | "column": 28 |
100 | }, | 100 | }, |
101 | "end": { | 101 | "end": { |
102 | "line": 50, | 102 | "line": 51, |
103 | "column": 3 | 103 | "column": 3 |
104 | } | 104 | } |
105 | } | 105 | } |
diff --git a/src/i18n/messages/src/helpers/plan-helpers.json b/src/i18n/messages/src/helpers/plan-helpers.json index df8ee19e3..3f3e7e85d 100644 --- a/src/i18n/messages/src/helpers/plan-helpers.json +++ b/src/i18n/messages/src/helpers/plan-helpers.json | |||
@@ -1,7 +1,7 @@ | |||
1 | [ | 1 | [ |
2 | { | 2 | { |
3 | "id": "pricing.plan.pro", | 3 | "id": "pricing.plan.pro", |
4 | "defaultMessage": "!!!Franz Professional", | 4 | "defaultMessage": "!!!Professional", |
5 | "file": "src/helpers/plan-helpers.js", | 5 | "file": "src/helpers/plan-helpers.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 5, | 7 | "line": 5, |
@@ -14,7 +14,7 @@ | |||
14 | }, | 14 | }, |
15 | { | 15 | { |
16 | "id": "pricing.plan.personal", | 16 | "id": "pricing.plan.personal", |
17 | "defaultMessage": "!!!Franz Personal", | 17 | "defaultMessage": "!!!Personal", |
18 | "file": "src/helpers/plan-helpers.js", | 18 | "file": "src/helpers/plan-helpers.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 9, | 20 | "line": 9, |
@@ -27,7 +27,7 @@ | |||
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | "id": "pricing.plan.free", | 29 | "id": "pricing.plan.free", |
30 | "defaultMessage": "!!!Franz Free", | 30 | "defaultMessage": "!!!Free", |
31 | "file": "src/helpers/plan-helpers.js", | 31 | "file": "src/helpers/plan-helpers.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 13, | 33 | "line": 13, |
@@ -40,7 +40,7 @@ | |||
40 | }, | 40 | }, |
41 | { | 41 | { |
42 | "id": "pricing.plan.legacy", | 42 | "id": "pricing.plan.legacy", |
43 | "defaultMessage": "!!!Franz Premium", | 43 | "defaultMessage": "!!!Premium", |
44 | "file": "src/helpers/plan-helpers.js", | 44 | "file": "src/helpers/plan-helpers.js", |
45 | "start": { | 45 | "start": { |
46 | "line": 17, | 46 | "line": 17, |
diff --git a/src/index.js b/src/index.js index a4880a25a..87aa6357b 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -68,8 +68,15 @@ if (isWindows) { | |||
68 | app.setAppUserModelId(appId); | 68 | app.setAppUserModelId(appId); |
69 | } | 69 | } |
70 | 70 | ||
71 | // Initialize Settings | ||
72 | const settings = new Settings('app', DEFAULT_APP_SETTINGS); | ||
73 | const proxySettings = new Settings('proxy'); | ||
74 | |||
75 | // add `liftSingleInstanceLock` to settings.json to override the single instance lock | ||
76 | const liftSingleInstanceLock = settings.get('liftSingleInstanceLock') || false; | ||
77 | |||
71 | // Force single window | 78 | // Force single window |
72 | const gotTheLock = app.requestSingleInstanceLock(); | 79 | const gotTheLock = liftSingleInstanceLock ? true : app.requestSingleInstanceLock(); |
73 | if (!gotTheLock) { | 80 | if (!gotTheLock) { |
74 | app.quit(); | 81 | app.quit(); |
75 | } else { | 82 | } else { |
@@ -116,10 +123,6 @@ if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESK | |||
116 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; | 123 | process.env.XDG_CURRENT_DESKTOP = 'Unity'; |
117 | } | 124 | } |
118 | 125 | ||
119 | // Initialize Settings | ||
120 | const settings = new Settings('app', DEFAULT_APP_SETTINGS); | ||
121 | const proxySettings = new Settings('proxy'); | ||
122 | |||
123 | // Disable GPU acceleration | 126 | // Disable GPU acceleration |
124 | if (!settings.get('enableGPUAcceleration')) { | 127 | if (!settings.get('enableGPUAcceleration')) { |
125 | debug('Disable GPU Acceleration'); | 128 | debug('Disable GPU Acceleration'); |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index f223283f9..d7398a126 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -730,7 +730,9 @@ export default class FranzMenu { | |||
730 | // need to clone object so we don't modify computed (cached) object | 730 | // need to clone object so we don't modify computed (cached) object |
731 | const serviceTpl = Object.assign([], this.serviceTpl()); | 731 | const serviceTpl = Object.assign([], this.serviceTpl()); |
732 | 732 | ||
733 | if (window.ferdi === undefined) { | 733 | // Don't initialize when window.franz is undefined or when we are on a payment window route |
734 | if (window.ferdi === undefined || this.stores.router.location.pathname.startsWith('/payment/')) { | ||
735 | console.log('skipping menu init'); | ||
734 | return; | 736 | return; |
735 | } | 737 | } |
736 | 738 | ||
diff --git a/src/lib/TouchBar.js b/src/lib/TouchBar.js index 1de46d2a3..32f546644 100644 --- a/src/lib/TouchBar.js +++ b/src/lib/TouchBar.js | |||
@@ -24,6 +24,10 @@ export default class FranzTouchBar { | |||
24 | _build() { | 24 | _build() { |
25 | const currentWindow = remote.getCurrentWindow(); | 25 | const currentWindow = remote.getCurrentWindow(); |
26 | 26 | ||
27 | if (this.stores.router.location.pathname.startsWith('/payment/')) { | ||
28 | return; | ||
29 | } | ||
30 | |||
27 | if (this.stores.user.isLoggedIn) { | 31 | if (this.stores.user.isLoggedIn) { |
28 | const { TouchBar } = remote; | 32 | const { TouchBar } = remote; |
29 | const { TouchBarButton, TouchBarSpacer } = TouchBar; | 33 | const { TouchBarButton, TouchBarSpacer } = TouchBar; |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 40d98cf42..c6724c20f 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -26,7 +26,9 @@ import { sleep } from '../helpers/async-helpers'; | |||
26 | 26 | ||
27 | const debug = require('debug')('Ferdi:AppStore'); | 27 | const debug = require('debug')('Ferdi:AppStore'); |
28 | 28 | ||
29 | const { app, systemPreferences, screen } = remote; | 29 | const { |
30 | app, systemPreferences, screen, powerMonitor, | ||
31 | } = remote; | ||
30 | 32 | ||
31 | const mainWindow = remote.getCurrentWindow(); | 33 | const mainWindow = remote.getCurrentWindow(); |
32 | 34 | ||
@@ -35,6 +37,8 @@ const autoLauncher = new AutoLaunch({ | |||
35 | name: 'Ferdi', | 37 | name: 'Ferdi', |
36 | }); | 38 | }); |
37 | 39 | ||
40 | const CATALINA_NOTIFICATION_HACK_KEY = '_temp_askedForCatalinaNotificationPermissions'; | ||
41 | |||
38 | export default class AppStore extends Store { | 42 | export default class AppStore extends Store { |
39 | updateStatusTypes = { | 43 | updateStatusTypes = { |
40 | CHECKING: 'CHECKING', | 44 | CHECKING: 'CHECKING', |
@@ -56,6 +60,8 @@ export default class AppStore extends Store { | |||
56 | 60 | ||
57 | @observable authRequestFailed = false; | 61 | @observable authRequestFailed = false; |
58 | 62 | ||
63 | @observable timeSuspensionStart; | ||
64 | |||
59 | @observable timeOfflineStart; | 65 | @observable timeOfflineStart; |
60 | 66 | ||
61 | @observable updateStatus = null; | 67 | @observable updateStatus = null; |
@@ -76,6 +82,8 @@ export default class AppStore extends Store { | |||
76 | 82 | ||
77 | dictionaries = []; | 83 | dictionaries = []; |
78 | 84 | ||
85 | fetchDataInterval = null; | ||
86 | |||
79 | constructor(...args) { | 87 | constructor(...args) { |
80 | super(...args); | 88 | super(...args); |
81 | 89 | ||
@@ -97,6 +105,7 @@ export default class AppStore extends Store { | |||
97 | this._setLocale.bind(this), | 105 | this._setLocale.bind(this), |
98 | this._muteAppHandler.bind(this), | 106 | this._muteAppHandler.bind(this), |
99 | this._handleFullScreen.bind(this), | 107 | this._handleFullScreen.bind(this), |
108 | this._handleLogout.bind(this), | ||
100 | ]); | 109 | ]); |
101 | } | 110 | } |
102 | 111 | ||
@@ -124,6 +133,12 @@ export default class AppStore extends Store { | |||
124 | this._systemDND(); | 133 | this._systemDND(); |
125 | setInterval(() => this._systemDND(), ms('5s')); | 134 | setInterval(() => this._systemDND(), ms('5s')); |
126 | 135 | ||
136 | this.fetchDataInterval = setInterval(() => { | ||
137 | this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); | ||
138 | this.stores.features.featuresRequest.invalidate({ immediately: true }); | ||
139 | this.stores.news.latestNewsRequest.invalidate({ immediately: true }); | ||
140 | }, ms('10m')); | ||
141 | |||
127 | // Check for updates once every 4 hours | 142 | // Check for updates once every 4 hours |
128 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); | 143 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); |
129 | // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues) | 144 | // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues) |
@@ -175,6 +190,36 @@ export default class AppStore extends Store { | |||
175 | 190 | ||
176 | debug('Window is visible/focused', isVisible); | 191 | debug('Window is visible/focused', isVisible); |
177 | }); | 192 | }); |
193 | |||
194 | powerMonitor.on('suspend', () => { | ||
195 | debug('System suspended starting timer'); | ||
196 | |||
197 | this.timeSuspensionStart = moment(); | ||
198 | }); | ||
199 | |||
200 | powerMonitor.on('resume', () => { | ||
201 | debug('System resumed, last suspended on', this.timeSuspensionStart.toString()); | ||
202 | |||
203 | if (this.timeSuspensionStart.add(10, 'm').isBefore(moment())) { | ||
204 | debug('Reloading services, user info and features'); | ||
205 | |||
206 | setTimeout(() => { | ||
207 | window.location.reload(); | ||
208 | }, ms('2s')); | ||
209 | } | ||
210 | }); | ||
211 | |||
212 | // macOS catalina notifications hack | ||
213 | // notifications got stuck after upgrade but forcing a notification | ||
214 | // via `new Notification` triggered the permission request | ||
215 | if (isMac && !localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { | ||
216 | // eslint-disable-next-line no-new | ||
217 | new window.Notification('Welcome to Franz 5', { | ||
218 | body: 'Have a wonderful day & happy messaging.', | ||
219 | }); | ||
220 | |||
221 | localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); | ||
222 | } | ||
178 | } | 223 | } |
179 | 224 | ||
180 | @computed get cacheSize() { | 225 | @computed get cacheSize() { |
@@ -383,6 +428,12 @@ export default class AppStore extends Store { | |||
383 | } | 428 | } |
384 | } | 429 | } |
385 | 430 | ||
431 | _handleLogout() { | ||
432 | if (!this.stores.user.isLoggedIn) { | ||
433 | clearInterval(this.fetchDataInterval); | ||
434 | } | ||
435 | } | ||
436 | |||
386 | // Helpers | 437 | // Helpers |
387 | _appStartsCounter() { | 438 | _appStartsCounter() { |
388 | this.actions.settings.update({ | 439 | this.actions.settings.update({ |
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 3d9542245..ab5d762c7 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js | |||
@@ -21,6 +21,8 @@ import serviceLimit from '../features/serviceLimit'; | |||
21 | import communityRecipes from '../features/communityRecipes'; | 21 | import communityRecipes from '../features/communityRecipes'; |
22 | import todos from '../features/todos'; | 22 | import todos from '../features/todos'; |
23 | import accentColor from '../features/accentColor'; | 23 | import accentColor from '../features/accentColor'; |
24 | import planSelection from '../features/planSelection'; | ||
25 | import trialStatusBar from '../features/trialStatusBar'; | ||
24 | 26 | ||
25 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 27 | import { DEFAULT_FEATURES_CONFIG } from '../config'; |
26 | 28 | ||
@@ -67,6 +69,7 @@ export default class FeaturesStore extends Store { | |||
67 | if (this.stores.user.isLoggedIn) { | 69 | if (this.stores.user.isLoggedIn) { |
68 | this.featuresRequest.invalidate({ immediately: true }); | 70 | this.featuresRequest.invalidate({ immediately: true }); |
69 | } else { | 71 | } else { |
72 | this.defaultFeaturesRequest.execute(); | ||
70 | this.defaultFeaturesRequest.invalidate({ immediately: true }); | 73 | this.defaultFeaturesRequest.invalidate({ immediately: true }); |
71 | } | 74 | } |
72 | } | 75 | } |
@@ -85,5 +88,7 @@ export default class FeaturesStore extends Store { | |||
85 | communityRecipes(this.stores, this.actions); | 88 | communityRecipes(this.stores, this.actions); |
86 | todos(this.stores, this.actions); | 89 | todos(this.stores, this.actions); |
87 | accentColor(this.stores, this.actions); | 90 | accentColor(this.stores, this.actions); |
91 | planSelection(this.stores, this.actions); | ||
92 | trialStatusBar(this.stores, this.actions); | ||
88 | } | 93 | } |
89 | } | 94 | } |
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js index 8579812ad..69e6eb9c3 100644 --- a/src/stores/PaymentStore.js +++ b/src/stores/PaymentStore.js | |||
@@ -1,9 +1,12 @@ | |||
1 | import { action, observable, computed } from 'mobx'; | 1 | import { action, observable, computed } from 'mobx'; |
2 | import { remote } from 'electron'; | ||
2 | 3 | ||
3 | import Store from './lib/Store'; | 4 | import Store from './lib/Store'; |
4 | import CachedRequest from './lib/CachedRequest'; | 5 | import CachedRequest from './lib/CachedRequest'; |
5 | import Request from './lib/Request'; | 6 | import Request from './lib/Request'; |
6 | 7 | ||
8 | const { BrowserWindow } = remote; | ||
9 | |||
7 | export default class PaymentStore extends Store { | 10 | export default class PaymentStore extends Store { |
8 | @observable plansRequest = new CachedRequest(this.api.payment, 'plans'); | 11 | @observable plansRequest = new CachedRequest(this.api.payment, 'plans'); |
9 | 12 | ||
@@ -13,6 +16,7 @@ export default class PaymentStore extends Store { | |||
13 | super(...args); | 16 | super(...args); |
14 | 17 | ||
15 | this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this)); | 18 | this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this)); |
19 | this.actions.payment.upgradeAccount.listen(this._upgradeAccount.bind(this)); | ||
16 | } | 20 | } |
17 | 21 | ||
18 | @computed get plan() { | 22 | @computed get plan() { |
@@ -27,4 +31,38 @@ export default class PaymentStore extends Store { | |||
27 | 31 | ||
28 | return request; | 32 | return request; |
29 | } | 33 | } |
34 | |||
35 | @action _upgradeAccount({ planId, onCloseWindow = () => null }) { | ||
36 | let hostedPageURL = this.stores.features.features.subscribeURL; | ||
37 | |||
38 | const parsedUrl = new URL(hostedPageURL); | ||
39 | const params = new URLSearchParams(parsedUrl.search.slice(1)); | ||
40 | |||
41 | params.set('plan', planId); | ||
42 | |||
43 | hostedPageURL = this.stores.user.getAuthURL(`${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`); | ||
44 | |||
45 | const win = new BrowserWindow({ | ||
46 | parent: remote.getCurrentWindow(), | ||
47 | modal: true, | ||
48 | title: '🔒 Upgrade Your Franz Account', | ||
49 | width: 800, | ||
50 | height: window.innerHeight - 100, | ||
51 | maxWidth: 800, | ||
52 | minWidth: 600, | ||
53 | autoHideMenuBar: true, | ||
54 | webPreferences: { | ||
55 | nodeIntegration: true, | ||
56 | webviewTag: true, | ||
57 | }, | ||
58 | }); | ||
59 | win.loadURL(`file://${__dirname}/../index.html#/payment/${encodeURIComponent(hostedPageURL)}`); | ||
60 | |||
61 | win.on('closed', () => { | ||
62 | this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); | ||
63 | this.stores.features.featuresRequest.invalidate({ immediately: true }); | ||
64 | |||
65 | onCloseWindow(); | ||
66 | }); | ||
67 | } | ||
30 | } | 68 | } |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 185a6f0ae..934a8a6e0 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import { shell } from 'electron'; | 1 | import { shell, remote } from 'electron'; |
2 | import { | 2 | import { |
3 | action, | 3 | action, |
4 | reaction, | 4 | reaction, |
@@ -23,6 +23,8 @@ import { KEEP_WS_LOADED_USID } from '../config'; | |||
23 | 23 | ||
24 | const debug = require('debug')('Ferdi:ServiceStore'); | 24 | const debug = require('debug')('Ferdi:ServiceStore'); |
25 | 25 | ||
26 | const { app } = remote; | ||
27 | |||
26 | export default class ServicesStore extends Store { | 28 | export default class ServicesStore extends Store { |
27 | @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); | 29 | @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); |
28 | 30 | ||
@@ -818,7 +820,9 @@ export default class ServicesStore extends Store { | |||
818 | 820 | ||
819 | if (service.webview) { | 821 | if (service.webview) { |
820 | debug('Initialize recipe', service.recipe.id, service.name); | 822 | debug('Initialize recipe', service.recipe.id, service.name); |
821 | service.webview.send('initialize-recipe', service.shareWithWebview, service.recipe); | 823 | service.webview.send('initialize-recipe', Object.assign({ |
824 | franzVersion: app.getVersion(), | ||
825 | }, service.shareWithWebview), service.recipe); | ||
822 | } | 826 | } |
823 | } | 827 | } |
824 | 828 | ||
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 03aa79606..d6a2e5fde 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -78,6 +78,8 @@ export default class UserStore extends Store { | |||
78 | 78 | ||
79 | @observable logoutReason = null; | 79 | @observable logoutReason = null; |
80 | 80 | ||
81 | fetchUserInfoInterval = null; | ||
82 | |||
81 | constructor(...args) { | 83 | constructor(...args) { |
82 | super(...args); | 84 | super(...args); |
83 | 85 | ||
@@ -162,7 +164,7 @@ export default class UserStore extends Store { | |||
162 | } | 164 | } |
163 | 165 | ||
164 | @computed get isPremiumOverride() { | 166 | @computed get isPremiumOverride() { |
165 | return ((!this.team || !this.team.plan) && this.isPremium) || (this.team.state === 'expired' && this.isPremium); | 167 | return ((!this.team || !this.team.plan) && this.isPremium) || (this.team && this.team.state === 'expired' && this.isPremium); |
166 | } | 168 | } |
167 | 169 | ||
168 | @computed get isPersonal() { | 170 | @computed get isPersonal() { |
@@ -201,7 +203,7 @@ export default class UserStore extends Store { | |||
201 | } | 203 | } |
202 | 204 | ||
203 | @action async _signup({ | 205 | @action async _signup({ |
204 | firstname, lastname, email, password, accountType, company, | 206 | firstname, lastname, email, password, accountType, company, plan, currency, |
205 | }) { | 207 | }) { |
206 | const authToken = await this.signupRequest.execute({ | 208 | const authToken = await this.signupRequest.execute({ |
207 | firstname, | 209 | firstname, |
@@ -211,6 +213,8 @@ export default class UserStore extends Store { | |||
211 | accountType, | 213 | accountType, |
212 | company, | 214 | company, |
213 | locale: this.stores.app.locale, | 215 | locale: this.stores.app.locale, |
216 | plan, | ||
217 | currency, | ||
214 | }); | 218 | }); |
215 | 219 | ||
216 | this.hasCompletedSignup = true; | 220 | this.hasCompletedSignup = true; |
diff --git a/src/stores/index.js b/src/stores/index.js index 10dd56665..4eeef7982 100644 --- a/src/stores/index.js +++ b/src/stores/index.js | |||
@@ -15,6 +15,7 @@ import { announcementsStore } from '../features/announcements'; | |||
15 | import { serviceLimitStore } from '../features/serviceLimit'; | 15 | import { serviceLimitStore } from '../features/serviceLimit'; |
16 | import { communityRecipesStore } from '../features/communityRecipes'; | 16 | import { communityRecipesStore } from '../features/communityRecipes'; |
17 | import { todosStore } from '../features/todos'; | 17 | import { todosStore } from '../features/todos'; |
18 | import { planSelectionStore } from '../features/planSelection'; | ||
18 | 19 | ||
19 | export default (api, actions, router) => { | 20 | export default (api, actions, router) => { |
20 | const stores = {}; | 21 | const stores = {}; |
@@ -37,6 +38,7 @@ export default (api, actions, router) => { | |||
37 | serviceLimit: serviceLimitStore, | 38 | serviceLimit: serviceLimitStore, |
38 | communityRecipes: communityRecipesStore, | 39 | communityRecipes: communityRecipesStore, |
39 | todos: todosStore, | 40 | todos: todosStore, |
41 | planSelection: planSelectionStore, | ||
40 | }); | 42 | }); |
41 | // Initialize all stores | 43 | // Initialize all stores |
42 | Object.keys(stores).forEach((name) => { | 44 | Object.keys(stores).forEach((name) => { |
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 753288b8d..324175d0b 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -373,7 +373,7 @@ | |||
373 | .account__subscription-button { margin-left: auto; } | 373 | .account__subscription-button { margin-left: auto; } |
374 | .franz-form__button { white-space: nowrap; } | 374 | .franz-form__button { white-space: nowrap; } |
375 | div { height: auto; } | 375 | div { height: auto; } |
376 | [data-type="franz-button"] div { height: 100% } | 376 | [data-type="franz-button"] div { height: 20px } |
377 | 377 | ||
378 | .invoices { | 378 | .invoices { |
379 | width: 100%; | 379 | width: 100%; |
diff --git a/src/styles/subscription-popup.scss b/src/styles/subscription-popup.scss index fb4795d6c..14e05e65d 100644 --- a/src/styles/subscription-popup.scss +++ b/src/styles/subscription-popup.scss | |||
@@ -2,7 +2,10 @@ | |||
2 | height: 100%; | 2 | height: 100%; |
3 | 3 | ||
4 | &__content { height: calc(100% - 60px); } | 4 | &__content { height: calc(100% - 60px); } |
5 | &__webview { height: 100%; } | 5 | &__webview { |
6 | height: 100%; | ||
7 | background: #FFF; | ||
8 | } | ||
6 | 9 | ||
7 | &__toolbar { | 10 | &__toolbar { |
8 | background: $theme-gray-lightest; | 11 | background: $theme-gray-lightest; |
diff --git a/src/webview/lib/RecipeWebview.js b/src/webview/lib/RecipeWebview.js index 877e45e35..74d05fc2d 100644 --- a/src/webview/lib/RecipeWebview.js +++ b/src/webview/lib/RecipeWebview.js | |||
@@ -1,7 +1,8 @@ | |||
1 | // @flow | ||
2 | const { ipcRenderer } = require('electron'); | 1 | const { ipcRenderer } = require('electron'); |
3 | const fs = require('fs-extra'); | 2 | const fs = require('fs-extra'); |
4 | 3 | ||
4 | const debug = require('debug')('Franz:Plugin:RecipeWebview'); | ||
5 | |||
5 | class RecipeWebview { | 6 | class RecipeWebview { |
6 | constructor() { | 7 | constructor() { |
7 | this.countCache = { | 8 | this.countCache = { |
@@ -11,6 +12,8 @@ class RecipeWebview { | |||
11 | 12 | ||
12 | ipcRenderer.on('poll', () => { | 13 | ipcRenderer.on('poll', () => { |
13 | this.loopFunc(); | 14 | this.loopFunc(); |
15 | |||
16 | debug('Poll event'); | ||
14 | }); | 17 | }); |
15 | } | 18 | } |
16 | 19 | ||
@@ -50,8 +53,11 @@ class RecipeWebview { | |||
50 | indirect: indirectInt > 0 ? indirectInt : 0, | 53 | indirect: indirectInt > 0 ? indirectInt : 0, |
51 | }; | 54 | }; |
52 | 55 | ||
56 | |||
53 | ipcRenderer.sendToHost('messages', count); | 57 | ipcRenderer.sendToHost('messages', count); |
54 | Object.assign(this.countCache, count); | 58 | Object.assign(this.countCache, count); |
59 | |||
60 | debug('Sending badge count to host', count); | ||
55 | } | 61 | } |
56 | 62 | ||
57 | /** | 63 | /** |
@@ -67,6 +73,8 @@ class RecipeWebview { | |||
67 | styles.innerHTML = data.toString(); | 73 | styles.innerHTML = data.toString(); |
68 | 74 | ||
69 | document.querySelector('head').appendChild(styles); | 75 | document.querySelector('head').appendChild(styles); |
76 | |||
77 | debug('Append styles', styles); | ||
70 | }); | 78 | }); |
71 | } | 79 | } |
72 | 80 | ||
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index ddfd0e139..2bf8f757a 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -242,7 +242,9 @@ window.open = (url, frameName, features) => { | |||
242 | return ipcRenderer.sendToHost('new-window', url); | 242 | return ipcRenderer.sendToHost('new-window', url); |
243 | } | 243 | } |
244 | 244 | ||
245 | return originalWindowOpen(url, frameName, features); | 245 | if (url) { |
246 | return originalWindowOpen(url, frameName, features); | ||
247 | } | ||
246 | }; | 248 | }; |
247 | 249 | ||
248 | if (isDevMode) { | 250 | if (isDevMode) { |