diff options
-rw-r--r-- | package-lock.json | 15 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | packages/theme/src/themes/dark/index.ts | 10 | ||||
-rw-r--r-- | packages/theme/src/themes/default/index.ts | 10 | ||||
-rw-r--r-- | src/actions/user.js | 3 | ||||
-rw-r--r-- | src/api/UserApi.js | 4 | ||||
-rw-r--r-- | src/api/server/ServerApi.js | 18 | ||||
-rw-r--r-- | src/components/auth/Pricing.js | 246 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 80 | ||||
-rw-r--r-- | src/components/ui/FeatureItem.js | 36 | ||||
-rw-r--r-- | src/components/ui/FeatureList.js | 89 | ||||
-rw-r--r-- | src/containers/auth/PricingScreen.js | 40 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 4 | ||||
-rw-r--r-- | src/i18n/messages/src/components/auth/Pricing.json | 101 | ||||
-rw-r--r-- | src/i18n/messages/src/components/services/content/Services.json | 8 | ||||
-rw-r--r-- | src/i18n/messages/src/components/ui/FeatureList.json | 132 | ||||
-rw-r--r-- | src/stores/UserStore.js | 19 | ||||
-rw-r--r-- | src/styles/auth.scss | 2 | ||||
-rw-r--r-- | src/styles/reset.scss | 1 |
19 files changed, 671 insertions, 148 deletions
diff --git a/package-lock.json b/package-lock.json index b4d86c5e1..13646552b 100644 --- a/package-lock.json +++ b/package-lock.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "name": "franz", | 2 | "name": "franz", |
3 | "version": "5.2.0-beta.3", | 3 | "version": "5.2.0-beta.4", |
4 | "lockfileVersion": 1, | 4 | "lockfileVersion": 1, |
5 | "requires": true, | 5 | "requires": true, |
6 | "dependencies": { | 6 | "dependencies": { |
@@ -16748,6 +16748,14 @@ | |||
16748 | "react-transition-group": "^1.2.0" | 16748 | "react-transition-group": "^1.2.0" |
16749 | } | 16749 | } |
16750 | }, | 16750 | }, |
16751 | "react-confetti": { | ||
16752 | "version": "3.1.0", | ||
16753 | "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-3.1.0.tgz", | ||
16754 | "integrity": "sha512-T1DKt09C9rrf2BJ0OxFu8saPohFalOJfOgci11/ePz6DxbY+0cd/3CMimxj/ZITl7jQWnmC/bNWBgGheke3WZQ==", | ||
16755 | "requires": { | ||
16756 | "tween-functions": "^1.2.0" | ||
16757 | } | ||
16758 | }, | ||
16751 | "react-dom": { | 16759 | "react-dom": { |
16752 | "version": "16.6.3", | 16760 | "version": "16.6.3", |
16753 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", | 16761 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", |
@@ -19899,6 +19907,11 @@ | |||
19899 | "safe-buffer": "^5.0.1" | 19907 | "safe-buffer": "^5.0.1" |
19900 | } | 19908 | } |
19901 | }, | 19909 | }, |
19910 | "tween-functions": { | ||
19911 | "version": "1.2.0", | ||
19912 | "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", | ||
19913 | "integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8=" | ||
19914 | }, | ||
19902 | "tweetnacl": { | 19915 | "tweetnacl": { |
19903 | "version": "0.14.5", | 19916 | "version": "0.14.5", |
19904 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", | 19917 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", |
diff --git a/package.json b/package.json index 7bf1f8a4a..98c7f547f 100644 --- a/package.json +++ b/package.json | |||
@@ -71,6 +71,7 @@ | |||
71 | "prop-types": "^15.5.10", | 71 | "prop-types": "^15.5.10", |
72 | "react": "16.6.3", | 72 | "react": "16.6.3", |
73 | "react-addons-css-transition-group": "15.6.2", | 73 | "react-addons-css-transition-group": "15.6.2", |
74 | "react-confetti": "3.1.0", | ||
74 | "react-dom": "16.6.3", | 75 | "react-dom": "16.6.3", |
75 | "react-dropzone": "7.0.1", | 76 | "react-dropzone": "7.0.1", |
76 | "react-electron-web-view": "^2.0.1", | 77 | "react-electron-web-view": "^2.0.1", |
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index bd9f001e8..1ea46cd1a 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts | |||
@@ -118,3 +118,13 @@ export const announcements = merge({}, defaultStyles.announcements, { | |||
118 | background: legacyStyles.darkThemeGrayDark, | 118 | background: legacyStyles.darkThemeGrayDark, |
119 | }, | 119 | }, |
120 | }); | 120 | }); |
121 | |||
122 | // Signup | ||
123 | export const signup = merge({}, defaultStyles.signup, { | ||
124 | pricing: { | ||
125 | feature: { | ||
126 | background: legacyStyles.darkThemeGrayLight, | ||
127 | border: color(legacyStyles.darkThemeGrayLight).lighten(0.2).hex(), | ||
128 | }, | ||
129 | }, | ||
130 | }); | ||
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts index 0f02fa3c8..0d99bc4be 100644 --- a/packages/theme/src/themes/default/index.ts +++ b/packages/theme/src/themes/default/index.ts | |||
@@ -207,3 +207,13 @@ export const announcements = { | |||
207 | background: legacyStyles.themeGrayLightest, | 207 | background: legacyStyles.themeGrayLightest, |
208 | }, | 208 | }, |
209 | }; | 209 | }; |
210 | |||
211 | // Signup | ||
212 | export const signup = { | ||
213 | pricing: { | ||
214 | feature: { | ||
215 | background: legacyStyles.themeGrayLightest, | ||
216 | border: legacyStyles.themeGrayLighter, | ||
217 | }, | ||
218 | }, | ||
219 | }; | ||
diff --git a/src/actions/user.js b/src/actions/user.js index ccf1fa56a..5d7d9a899 100644 --- a/src/actions/user.js +++ b/src/actions/user.js | |||
@@ -17,6 +17,9 @@ export default { | |||
17 | retrievePassword: { | 17 | retrievePassword: { |
18 | email: PropTypes.string.isRequired, | 18 | email: PropTypes.string.isRequired, |
19 | }, | 19 | }, |
20 | activateTrial: { | ||
21 | planId: PropTypes.string.isRequired, | ||
22 | }, | ||
20 | invite: { | 23 | invite: { |
21 | invites: PropTypes.array.isRequired, | 24 | invites: PropTypes.array.isRequired, |
22 | }, | 25 | }, |
diff --git a/src/api/UserApi.js b/src/api/UserApi.js index edfb88988..8ba8cd1e9 100644 --- a/src/api/UserApi.js +++ b/src/api/UserApi.js | |||
@@ -25,6 +25,10 @@ export default class UserApi { | |||
25 | return this.server.retrievePassword(email); | 25 | return this.server.retrievePassword(email); |
26 | } | 26 | } |
27 | 27 | ||
28 | activateTrial(data) { | ||
29 | return this.server.activateTrial(data); | ||
30 | } | ||
31 | |||
28 | invite(data) { | 32 | invite(data) { |
29 | return this.server.inviteUser(data); | 33 | return this.server.inviteUser(data); |
30 | } | 34 | } |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index a9ce202ff..f2568d597 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -77,6 +77,22 @@ export default class ServerApi { | |||
77 | return u.token; | 77 | return u.token; |
78 | } | 78 | } |
79 | 79 | ||
80 | async activateTrial(data) { | ||
81 | const request = await sendAuthRequest(`${API_URL}/payment/trial`, { | ||
82 | method: 'POST', | ||
83 | body: JSON.stringify(data), | ||
84 | }); | ||
85 | if (!request.ok) { | ||
86 | throw request; | ||
87 | } | ||
88 | const trial = await request.json(); | ||
89 | |||
90 | console.log(trial); | ||
91 | |||
92 | debug('ServerApi::signup resolves', trial); | ||
93 | return true; | ||
94 | } | ||
95 | |||
80 | async inviteUser(data) { | 96 | async inviteUser(data) { |
81 | const request = await sendAuthRequest(`${API_URL}/invite`, { | 97 | const request = await sendAuthRequest(`${API_URL}/invite`, { |
82 | method: 'POST', | 98 | method: 'POST', |
@@ -469,7 +485,7 @@ export default class ServerApi { | |||
469 | return services; | 485 | return services; |
470 | } | 486 | } |
471 | } catch (err) { | 487 | } catch (err) { |
472 | throw (new Error('ServerApi::getLegacyServices no config found')); | 488 | console.error('ServerApi::getLegacyServices no config found'); |
473 | } | 489 | } |
474 | 490 | ||
475 | return []; | 491 | return []; |
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 7ab14f429..d20779025 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js | |||
@@ -1,40 +1,107 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | // import { Link } from 'react-router'; | 5 | import injectSheet from 'react-jss'; |
6 | import { H2, Loader } from '@meetfranz/ui'; | ||
7 | import classnames from 'classnames'; | ||
8 | |||
9 | import { Button } from '@meetfranz/forms'; | ||
10 | import { FeatureItem } from '../ui/FeatureItem'; | ||
11 | import FeatureList from '../ui/FeatureList'; | ||
6 | 12 | ||
7 | // import Button from '../ui/Button'; | ||
8 | import Loader from '../ui/Loader'; | ||
9 | import Appear from '../ui/effects/Appear'; | ||
10 | import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen'; | ||
11 | 13 | ||
12 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
13 | headline: { | 15 | headline: { |
14 | id: 'pricing.headline', | 16 | id: 'pricing.trial.headline', |
15 | defaultMessage: '!!!Support Franz', | 17 | defaultMessage: '!!!Franz Professional', |
18 | }, | ||
19 | personalOffer: { | ||
20 | id: 'pricing.trial.subheadline', | ||
21 | defaultMessage: '!!!Your personal welcome offer:', | ||
22 | }, | ||
23 | noStringsAttachedHeadline: { | ||
24 | id: 'pricing.trial.terms.headline', | ||
25 | defaultMessage: '!!!No strings attached', | ||
26 | }, | ||
27 | noCreditCard: { | ||
28 | id: 'pricing.trial.terms.noCreditCard', | ||
29 | defaultMessage: '!!!No credit card required', | ||
16 | }, | 30 | }, |
17 | monthlySupportLabel: { | 31 | automaticTrialEnd: { |
18 | id: 'pricing.support.label', | 32 | id: 'pricing.trial.terms.automaticTrialEnd', |
19 | defaultMessage: '!!!Select your support plan', | 33 | defaultMessage: '!!!Your free trial ends automatically after 14 days', |
20 | }, | 34 | }, |
21 | submitButtonLabel: { | 35 | activationError: { |
22 | id: 'pricing.submit.label', | 36 | id: 'pricing.trial.error', |
23 | defaultMessage: '!!!Support the development of Franz', | 37 | defaultMessage: '!!!Sorry, we could not activate your trial!', |
24 | }, | 38 | }, |
25 | skipPayment: { | 39 | ctaAccept: { |
26 | id: 'pricing.link.skipPayment', | 40 | id: 'pricing.trial.cta.accept', |
27 | defaultMessage: '!!!I don\'t want to support the development of Franz.', | 41 | defaultMessage: '!!!Yes, upgrade my account to Franz Professional', |
42 | }, | ||
43 | ctaSkip: { | ||
44 | id: 'pricing.trial.cta.skip', | ||
45 | defaultMessage: '!!!Continue to Franz', | ||
46 | }, | ||
47 | featuresHeadline: { | ||
48 | id: 'pricing.trial.features.headline', | ||
49 | defaultMessage: '!!!Franz Professional includes:', | ||
28 | }, | 50 | }, |
29 | }); | 51 | }); |
30 | 52 | ||
31 | export default @observer class Signup extends Component { | 53 | const styles = theme => ({ |
54 | container: { | ||
55 | position: 'relative', | ||
56 | marginLeft: -150, | ||
57 | }, | ||
58 | welcomeOffer: { | ||
59 | textAlign: 'center', | ||
60 | fontWeight: 'bold', | ||
61 | }, | ||
62 | keyTerms: { | ||
63 | textAlign: 'center', | ||
64 | }, | ||
65 | content: { | ||
66 | position: 'relative', | ||
67 | zIndex: 20, | ||
68 | }, | ||
69 | featureContainer: { | ||
70 | width: 300, | ||
71 | position: 'absolute', | ||
72 | left: 'calc(100% / 2 + 225px)', | ||
73 | top: 155, | ||
74 | background: theme.signup.pricing.feature.background, | ||
75 | height: 'auto', | ||
76 | padding: 20, | ||
77 | borderTopRightRadius: theme.borderRadius, | ||
78 | borderBottomRightRadius: theme.borderRadius, | ||
79 | zIndex: 10, | ||
80 | }, | ||
81 | featureItem: { | ||
82 | borderBottom: [1, 'solid', theme.signup.pricing.feature.border], | ||
83 | }, | ||
84 | cta: { | ||
85 | marginTop: 40, | ||
86 | width: '100%', | ||
87 | }, | ||
88 | skipLink: { | ||
89 | textAlign: 'center', | ||
90 | marginTop: 10, | ||
91 | }, | ||
92 | error: { | ||
93 | margin: [20, 0, 0], | ||
94 | color: theme.styleTypes.danger.accent, | ||
95 | }, | ||
96 | }); | ||
97 | |||
98 | export default @observer @injectSheet(styles) class Signup extends Component { | ||
32 | static propTypes = { | 99 | static propTypes = { |
33 | donor: MobxPropTypes.objectOrObservableObject.isRequired, | 100 | onSubmit: PropTypes.func.isRequired, |
34 | isLoading: PropTypes.bool.isRequired, | 101 | isLoadingRequiredData: PropTypes.bool.isRequired, |
35 | isLoadingUser: PropTypes.bool.isRequired, | 102 | isActivatingTrial: PropTypes.bool.isRequired, |
36 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | 103 | trialActivationError: PropTypes.bool.isRequired, |
37 | skipAction: PropTypes.func.isRequired, | 104 | classes: PropTypes.object.isRequired, |
38 | }; | 105 | }; |
39 | 106 | ||
40 | static contextTypes = { | 107 | static contextTypes = { |
@@ -43,70 +110,37 @@ export default @observer class Signup extends Component { | |||
43 | 110 | ||
44 | render() { | 111 | render() { |
45 | const { | 112 | const { |
46 | donor, | 113 | onSubmit, |
47 | isLoading, | 114 | isLoadingRequiredData, |
48 | isLoadingUser, | 115 | isActivatingTrial, |
49 | onCloseSubscriptionWindow, | 116 | trialActivationError, |
50 | skipAction, | 117 | classes, |
51 | } = this.props; | 118 | } = this.props; |
52 | const { intl } = this.context; | 119 | const { intl } = this.context; |
53 | 120 | ||
54 | return ( | 121 | return ( |
55 | <div className="auth__scroll-container"> | 122 | <div className={classnames('auth__scroll-container', classes.container)}> |
56 | <div className="auth__container auth__container--signup"> | 123 | <div className={classnames('auth__container', 'auth__container--signup', classes.content)}> |
57 | <form className="franz-form auth__form"> | 124 | <form className="franz-form auth__form"> |
58 | <img | 125 | {isLoadingRequiredData ? <Loader /> : ( |
59 | src="./assets/images/sm.png" | 126 | <img |
60 | className="auth__logo auth__logo--sm" | 127 | src="./assets/images/sm.png" |
61 | alt="" | 128 | className="auth__logo auth__logo--sm" |
62 | /> | 129 | alt="" |
130 | /> | ||
131 | )} | ||
132 | <p className={classes.welcomeOffer}>{intl.formatMessage(messages.personalOffer)}</p> | ||
63 | <h1>{intl.formatMessage(messages.headline)}</h1> | 133 | <h1>{intl.formatMessage(messages.headline)}</h1> |
64 | <div className="auth__letter"> | 134 | <div className="auth__letter"> |
65 | {isLoadingUser && ( | 135 | <p> |
66 | <p>Loading</p> | 136 | We built Franz with a lot of effort, manpower and love, |
67 | )} | 137 | to boost up your messaging experience. |
68 | {!isLoadingUser && ( | 138 | <br /> |
69 | donor.amount ? ( | 139 | </p> |
70 | <span> | 140 | <p> |
71 | <p> | 141 | Get the free 14 day Franz Professional trial and see your communication evolving. |
72 | Thank you so much for your previous donation of | 142 | <br /> |
73 | {' '} | 143 | </p> |
74 | <strong> | ||
75 | $ | ||
76 | {donor.amount} | ||
77 | </strong> | ||
78 | . | ||
79 | <br /> | ||
80 | Your support allowed us to get where we are today. | ||
81 | <br /> | ||
82 | </p> | ||
83 | <p> | ||
84 | As an early supporter, you get | ||
85 | {' '} | ||
86 | <strong>a lifetime premium supporter license</strong> | ||
87 | {' '} | ||
88 | without any | ||
89 | additional charges. | ||
90 | </p> | ||
91 | <p> | ||
92 | However, If you want to keep supporting us, you are more than welcome to subscribe to a plan. | ||
93 | <br /> | ||
94 | <br /> | ||
95 | </p> | ||
96 | </span> | ||
97 | ) : ( | ||
98 | <span> | ||
99 | <p> | ||
100 | We built Franz with a lot of effort, manpower and love, | ||
101 | to bring you the best messaging experience. | ||
102 | <br /> | ||
103 | </p> | ||
104 | <p> | ||
105 | Getting a Franz Premium Supporter License will allow us to keep improving Franz for you. | ||
106 | </p> | ||
107 | </span> | ||
108 | ) | ||
109 | )} | ||
110 | <p> | 144 | <p> |
111 | Thanks for being a hero. | 145 | Thanks for being a hero. |
112 | </p> | 146 | </p> |
@@ -114,20 +148,48 @@ export default @observer class Signup extends Component { | |||
114 | <strong>Stefan Malzner</strong> | 148 | <strong>Stefan Malzner</strong> |
115 | </p> | 149 | </p> |
116 | </div> | 150 | </div> |
117 | <Loader loaded={!isLoading}> | 151 | <div className={classes.keyTerms}> |
118 | <Appear transitionName="slideDown"> | 152 | <H2> |
119 | <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span> | 153 | {intl.formatMessage(messages.noStringsAttachedHeadline)} |
120 | <SubscriptionForm | 154 | </H2> |
121 | onCloseWindow={onCloseSubscriptionWindow} | 155 | <ul className={classes.keyTermsList}> |
122 | showSkipOption | 156 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> |
123 | skipAction={skipAction} | 157 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> |
124 | hideInfo={Boolean(donor.amount)} | 158 | </ul> |
125 | skipButtonLabel={intl.formatMessage(messages.skipPayment)} | 159 | </div> |
126 | /> | 160 | {trialActivationError && ( |
127 | </Appear> | 161 | <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p> |
128 | </Loader> | 162 | )} |
163 | <Button | ||
164 | label={intl.formatMessage(messages.ctaAccept)} | ||
165 | className={classes.cta} | ||
166 | onClick={onSubmit} | ||
167 | busy={isActivatingTrial} | ||
168 | disabled={isLoadingRequiredData || isActivatingTrial} | ||
169 | /> | ||
170 | <p className={classes.skipLink}> | ||
171 | <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a> | ||
172 | </p> | ||
129 | </form> | 173 | </form> |
130 | </div> | 174 | </div> |
175 | <div className={classes.featureContainer}> | ||
176 | <H2> | ||
177 | {intl.formatMessage(messages.featuresHeadline)} | ||
178 | </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 /> | ||
192 | </div> | ||
131 | </div> | 193 | </div> |
132 | ); | 194 | ); |
133 | } | 195 | } |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 48de0ebaa..7f1624003 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -3,6 +3,9 @@ import PropTypes from 'prop-types'; | |||
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import Confetti from 'react-confetti'; | ||
7 | import ms from 'ms'; | ||
8 | import injectSheet from 'react-jss'; | ||
6 | 9 | ||
7 | import ServiceView from './ServiceView'; | 10 | import ServiceView from './ServiceView'; |
8 | import Appear from '../../ui/effects/Appear'; | 11 | import Appear from '../../ui/effects/Appear'; |
@@ -18,7 +21,16 @@ const messages = defineMessages({ | |||
18 | }, | 21 | }, |
19 | }); | 22 | }); |
20 | 23 | ||
21 | export default @observer class Services extends Component { | 24 | |
25 | const styles = { | ||
26 | confettiContainer: { | ||
27 | position: 'absolute', | ||
28 | width: '100%', | ||
29 | zIndex: 0, | ||
30 | }, | ||
31 | }; | ||
32 | |||
33 | export default @observer @injectSheet(styles) class Services extends Component { | ||
22 | static propTypes = { | 34 | static propTypes = { |
23 | services: MobxPropTypes.arrayOrObservableArray, | 35 | services: MobxPropTypes.arrayOrObservableArray, |
24 | setWebviewReference: PropTypes.func.isRequired, | 36 | setWebviewReference: PropTypes.func.isRequired, |
@@ -28,6 +40,8 @@ export default @observer class Services extends Component { | |||
28 | reload: PropTypes.func.isRequired, | 40 | reload: PropTypes.func.isRequired, |
29 | openSettings: PropTypes.func.isRequired, | 41 | openSettings: PropTypes.func.isRequired, |
30 | update: PropTypes.func.isRequired, | 42 | update: PropTypes.func.isRequired, |
43 | userHasCompletedSignup: PropTypes.bool.isRequired, | ||
44 | classes: PropTypes.object.isRequired, | ||
31 | }; | 45 | }; |
32 | 46 | ||
33 | static defaultProps = { | 47 | static defaultProps = { |
@@ -38,6 +52,18 @@ export default @observer class Services extends Component { | |||
38 | intl: intlShape, | 52 | intl: intlShape, |
39 | }; | 53 | }; |
40 | 54 | ||
55 | state = { | ||
56 | showConfetti: true, | ||
57 | } | ||
58 | |||
59 | componentDidMount() { | ||
60 | window.setTimeout(() => { | ||
61 | this.setState({ | ||
62 | showConfetti: false, | ||
63 | }); | ||
64 | }, ms('8s')); | ||
65 | } | ||
66 | |||
41 | render() { | 67 | render() { |
42 | const { | 68 | const { |
43 | services, | 69 | services, |
@@ -48,29 +74,47 @@ export default @observer class Services extends Component { | |||
48 | reload, | 74 | reload, |
49 | openSettings, | 75 | openSettings, |
50 | update, | 76 | update, |
77 | userHasCompletedSignup, | ||
78 | classes, | ||
51 | } = this.props; | 79 | } = this.props; |
80 | |||
81 | const { | ||
82 | showConfetti, | ||
83 | } = this.state; | ||
84 | |||
52 | const { intl } = this.context; | 85 | const { intl } = this.context; |
53 | 86 | ||
54 | return ( | 87 | return ( |
55 | <div className="services"> | 88 | <div className="services"> |
56 | {services.length === 0 && ( | 89 | {services.length === 0 && ( |
57 | <Appear | 90 | <> |
58 | timeout={1500} | 91 | {userHasCompletedSignup && ( |
59 | transitionName="slideUp" | 92 | <div className={classes.confettiContainer}> |
60 | > | 93 | <Confetti |
61 | <div className="services__no-service"> | 94 | width={window.width} |
62 | <img src="./assets/images/logo.svg" alt="" /> | 95 | height={window.height} |
63 | <h1>{intl.formatMessage(messages.welcome)}</h1> | 96 | numberOfPieces={showConfetti ? 200 : 0} |
64 | <Appear | 97 | /> |
65 | timeout={300} | 98 | </div> |
66 | transitionName="slideUp" | 99 | )} |
67 | > | 100 | <Appear |
68 | <Link to="/settings/recipes" className="button"> | 101 | timeout={1500} |
69 | {intl.formatMessage(messages.getStarted)} | 102 | transitionName="slideUp" |
70 | </Link> | 103 | > |
71 | </Appear> | 104 | <div className="services__no-service"> |
72 | </div> | 105 | <img src="./assets/images/logo.svg" alt="" /> |
73 | </Appear> | 106 | <h1>{intl.formatMessage(messages.welcome)}</h1> |
107 | <Appear | ||
108 | timeout={300} | ||
109 | transitionName="slideUp" | ||
110 | > | ||
111 | <Link to="/settings/recipes" className="button"> | ||
112 | {intl.formatMessage(messages.getStarted)} | ||
113 | </Link> | ||
114 | </Appear> | ||
115 | </div> | ||
116 | </Appear> | ||
117 | </> | ||
74 | )} | 118 | )} |
75 | {services.map(service => ( | 119 | {services.map(service => ( |
76 | <ServiceView | 120 | <ServiceView |
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js new file mode 100644 index 000000000..a63f5f7b5 --- /dev/null +++ b/src/components/ui/FeatureItem.js | |||
@@ -0,0 +1,36 @@ | |||
1 | import React from 'react'; | ||
2 | import injectSheet from 'react-jss'; | ||
3 | import { Icon } from '@meetfranz/ui'; | ||
4 | import classnames from 'classnames'; | ||
5 | |||
6 | const styles = theme => ({ | ||
7 | featureItem: { | ||
8 | borderBottom: [1, 'solid', theme.legacyStyles.themeGrayDark], | ||
9 | padding: [8, 0], | ||
10 | display: 'flex', | ||
11 | alignItems: 'center', | ||
12 | }, | ||
13 | featureIcon: { | ||
14 | fill: theme.brandSuccess, | ||
15 | marginRight: 10, | ||
16 | }, | ||
17 | }); | ||
18 | |||
19 | export const FeatureItem = injectSheet(styles)(({ | ||
20 | classes, className, name, icon, | ||
21 | }) => ( | ||
22 | <li className={classnames({ | ||
23 | [classes.featureItem]: true, | ||
24 | [className]: className, | ||
25 | })} | ||
26 | > | ||
27 | {icon ? ( | ||
28 | <span className={classes.featureIcon}>{icon}</span> | ||
29 | ) : ( | ||
30 | <Icon icon="mdiCheckCircle" className={classes.featureIcon} size={1.5} /> | ||
31 | )} | ||
32 | {name} | ||
33 | </li> | ||
34 | )); | ||
35 | |||
36 | export default FeatureItem; | ||
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js new file mode 100644 index 000000000..62944ad75 --- /dev/null +++ b/src/components/ui/FeatureList.js | |||
@@ -0,0 +1,89 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { defineMessages, intlShape } from 'react-intl'; | ||
4 | |||
5 | import { FeatureItem } from './FeatureItem'; | ||
6 | |||
7 | const messages = defineMessages({ | ||
8 | unlimitedServices: { | ||
9 | id: 'pricing.features.unlimitedServices', | ||
10 | defaultMessage: '!!!Add unlimited services', | ||
11 | }, | ||
12 | spellchecker: { | ||
13 | id: 'pricing.features.spellchecker', | ||
14 | defaultMessage: '!!!Spellchecker support', | ||
15 | }, | ||
16 | workspaces: { | ||
17 | id: 'pricing.features.workspaces', | ||
18 | defaultMessage: '!!!Workspaces', | ||
19 | }, | ||
20 | customWebsites: { | ||
21 | id: 'pricing.features.customWebsites', | ||
22 | defaultMessage: '!!!Add Custom Websites', | ||
23 | }, | ||
24 | onPremise: { | ||
25 | id: 'pricing.features.onPremise', | ||
26 | defaultMessage: '!!!On-premise & other Hosted Services', | ||
27 | }, | ||
28 | thirdPartyServices: { | ||
29 | id: 'pricing.features.thirdPartyServices', | ||
30 | defaultMessage: '!!!Install 3rd party services', | ||
31 | }, | ||
32 | serviceProxies: { | ||
33 | id: 'pricing.features.serviceProxies', | ||
34 | defaultMessage: '!!!Service Proxies', | ||
35 | }, | ||
36 | teamManagement: { | ||
37 | id: 'pricing.features.teamManagement', | ||
38 | defaultMessage: '!!!Team Management', | ||
39 | }, | ||
40 | appDelays: { | ||
41 | id: 'pricing.features.appDelays', | ||
42 | defaultMessage: '!!!No Waiting Screens', | ||
43 | }, | ||
44 | adFree: { | ||
45 | id: 'pricing.features.adFree', | ||
46 | defaultMessage: '!!!Forever ad-free', | ||
47 | }, | ||
48 | }); | ||
49 | |||
50 | export class FeatureList extends Component { | ||
51 | static propTypes = { | ||
52 | className: PropTypes.string, | ||
53 | featureClassName: PropTypes.string, | ||
54 | }; | ||
55 | |||
56 | static defaultProps = { | ||
57 | className: '', | ||
58 | featureClassName: '', | ||
59 | } | ||
60 | |||
61 | static contextTypes = { | ||
62 | intl: intlShape, | ||
63 | }; | ||
64 | |||
65 | render() { | ||
66 | const { | ||
67 | className, | ||
68 | featureClassName, | ||
69 | } = this.props; | ||
70 | const { intl } = this.context; | ||
71 | |||
72 | return ( | ||
73 | <ul className={className}> | ||
74 | <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} 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> | ||
85 | ); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | export default FeatureList; | ||
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index 8d179a170..af1651931 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js | |||
@@ -5,7 +5,6 @@ import { RouterStore } from 'mobx-react-router'; | |||
5 | 5 | ||
6 | import Pricing from '../../components/auth/Pricing'; | 6 | import Pricing from '../../components/auth/Pricing'; |
7 | import UserStore from '../../stores/UserStore'; | 7 | import UserStore from '../../stores/UserStore'; |
8 | import PaymentStore from '../../stores/PaymentStore'; | ||
9 | 8 | ||
10 | import { globalError as globalErrorPropType } from '../../prop-types'; | 9 | import { globalError as globalErrorPropType } from '../../prop-types'; |
11 | 10 | ||
@@ -14,20 +13,40 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend | |||
14 | error: globalErrorPropType.isRequired, | 13 | error: globalErrorPropType.isRequired, |
15 | }; | 14 | }; |
16 | 15 | ||
16 | async submit() { | ||
17 | const { | ||
18 | actions, | ||
19 | stores, | ||
20 | } = this.props; | ||
21 | |||
22 | const { activateTrialRequest } = stores.user; | ||
23 | const { defaultTrialPlan } = stores.features.features; | ||
24 | |||
25 | actions.user.activateTrial({ planId: defaultTrialPlan }); | ||
26 | await activateTrialRequest._promise; | ||
27 | |||
28 | if (!activateTrialRequest.isError) { | ||
29 | stores.router.push('/'); | ||
30 | stores.user.hasCompletedSignup = true; | ||
31 | } | ||
32 | } | ||
33 | |||
17 | render() { | 34 | render() { |
18 | const { actions, stores, error } = this.props; | 35 | const { |
36 | error, | ||
37 | stores, | ||
38 | } = this.props; | ||
19 | 39 | ||
20 | const nextStepRoute = stores.user.legacyServices.length ? stores.user.importRoute : stores.user.inviteRoute; | 40 | const { getUserInfoRequest, activateTrialRequest } = stores.user; |
41 | const { featuresRequest } = stores.features; | ||
21 | 42 | ||
22 | return ( | 43 | return ( |
23 | <Pricing | 44 | <Pricing |
24 | donor={stores.user.data.donor || {}} | 45 | onSubmit={this.submit.bind(this)} |
25 | onSubmit={actions.user.signup} | 46 | isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)} |
26 | onCloseSubscriptionWindow={() => this.props.stores.router.push(nextStepRoute)} | 47 | isActivatingTrial={activateTrialRequest.isExecuting} |
27 | isLoading={stores.payment.plansRequest.isExecuting} | 48 | trialActivationError={activateTrialRequest.isError} |
28 | isLoadingUser={stores.user.getUserInfoRequest.isExecuting} | ||
29 | error={error} | 49 | error={error} |
30 | skipAction={() => this.props.stores.router.push(nextStepRoute)} | ||
31 | /> | 50 | /> |
32 | ); | 51 | ); |
33 | } | 52 | } |
@@ -36,12 +55,11 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend | |||
36 | PricingScreen.wrappedComponent.propTypes = { | 55 | PricingScreen.wrappedComponent.propTypes = { |
37 | actions: PropTypes.shape({ | 56 | actions: PropTypes.shape({ |
38 | user: PropTypes.shape({ | 57 | user: PropTypes.shape({ |
39 | signup: PropTypes.func.isRequired, | 58 | activateTrial: PropTypes.func.isRequired, |
40 | }).isRequired, | 59 | }).isRequired, |
41 | }).isRequired, | 60 | }).isRequired, |
42 | stores: PropTypes.shape({ | 61 | stores: PropTypes.shape({ |
43 | user: PropTypes.instanceOf(UserStore).isRequired, | 62 | user: PropTypes.instanceOf(UserStore).isRequired, |
44 | payment: PropTypes.instanceOf(PaymentStore).isRequired, | ||
45 | router: PropTypes.instanceOf(RouterStore).isRequired, | 63 | router: PropTypes.instanceOf(RouterStore).isRequired, |
46 | }).isRequired, | 64 | }).isRequired, |
47 | }; | 65 | }; |
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index cf3da71e8..b4e32cffa 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -10,6 +10,7 @@ import FeaturesStore from '../../stores/FeaturesStore'; | |||
10 | import UIStore from '../../stores/UIStore'; | 10 | import UIStore from '../../stores/UIStore'; |
11 | import NewsStore from '../../stores/NewsStore'; | 11 | import NewsStore from '../../stores/NewsStore'; |
12 | import SettingsStore from '../../stores/SettingsStore'; | 12 | import SettingsStore from '../../stores/SettingsStore'; |
13 | import UserStore from '../../stores/UserStore'; | ||
13 | import RequestStore from '../../stores/RequestStore'; | 14 | import RequestStore from '../../stores/RequestStore'; |
14 | import GlobalErrorStore from '../../stores/GlobalErrorStore'; | 15 | import GlobalErrorStore from '../../stores/GlobalErrorStore'; |
15 | 16 | ||
@@ -39,6 +40,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
39 | settings, | 40 | settings, |
40 | globalError, | 41 | globalError, |
41 | requests, | 42 | requests, |
43 | user, | ||
42 | } = this.props.stores; | 44 | } = this.props.stores; |
43 | 45 | ||
44 | const { | 46 | const { |
@@ -125,6 +127,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
125 | reload={reload} | 127 | reload={reload} |
126 | openSettings={openSettings} | 128 | openSettings={openSettings} |
127 | update={updateService} | 129 | update={updateService} |
130 | userHasCompletedSignup={user.hasCompletedSignup} | ||
128 | /> | 131 | /> |
129 | ); | 132 | ); |
130 | 133 | ||
@@ -166,6 +169,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { | |||
166 | ui: PropTypes.instanceOf(UIStore).isRequired, | 169 | ui: PropTypes.instanceOf(UIStore).isRequired, |
167 | news: PropTypes.instanceOf(NewsStore).isRequired, | 170 | news: PropTypes.instanceOf(NewsStore).isRequired, |
168 | settings: PropTypes.instanceOf(SettingsStore).isRequired, | 171 | settings: PropTypes.instanceOf(SettingsStore).isRequired, |
172 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
169 | requests: PropTypes.instanceOf(RequestStore).isRequired, | 173 | requests: PropTypes.instanceOf(RequestStore).isRequired, |
170 | globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, | 174 | globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, |
171 | }).isRequired, | 175 | }).isRequired, |
diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json index f711a55b4..f15617ca5 100644 --- a/src/i18n/messages/src/components/auth/Pricing.json +++ b/src/i18n/messages/src/components/auth/Pricing.json | |||
@@ -1,53 +1,118 @@ | |||
1 | [ | 1 | [ |
2 | { | 2 | { |
3 | "id": "pricing.headline", | 3 | "id": "pricing.trial.headline", |
4 | "defaultMessage": "!!!Support Franz", | 4 | "defaultMessage": "!!!Franz Professional", |
5 | "file": "src/components/auth/Pricing.js", | 5 | "file": "src/components/auth/Pricing.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 13, | 7 | "line": 15, |
8 | "column": 12 | 8 | "column": 12 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 16, | 11 | "line": 18, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
15 | { | 15 | { |
16 | "id": "pricing.support.label", | 16 | "id": "pricing.trial.subheadline", |
17 | "defaultMessage": "!!!Select your support plan", | 17 | "defaultMessage": "!!!Your personal welcome offer:", |
18 | "file": "src/components/auth/Pricing.js", | 18 | "file": "src/components/auth/Pricing.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 17, | 20 | "line": 19, |
21 | "column": 23 | 21 | "column": 17 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 20, | 24 | "line": 22, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | }, | 27 | }, |
28 | { | 28 | { |
29 | "id": "pricing.submit.label", | 29 | "id": "pricing.trial.terms.headline", |
30 | "defaultMessage": "!!!Support the development of Franz", | 30 | "defaultMessage": "!!!No strings attached", |
31 | "file": "src/components/auth/Pricing.js", | 31 | "file": "src/components/auth/Pricing.js", |
32 | "start": { | 32 | "start": { |
33 | "line": 21, | 33 | "line": 23, |
34 | "column": 29 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 26, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "pricing.trial.terms.noCreditCard", | ||
43 | "defaultMessage": "!!!No credit card required", | ||
44 | "file": "src/components/auth/Pricing.js", | ||
45 | "start": { | ||
46 | "line": 27, | ||
47 | "column": 16 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 30, | ||
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | "id": "pricing.trial.terms.automaticTrialEnd", | ||
56 | "defaultMessage": "!!!Your free trial ends automatically after 14 days", | ||
57 | "file": "src/components/auth/Pricing.js", | ||
58 | "start": { | ||
59 | "line": 31, | ||
34 | "column": 21 | 60 | "column": 21 |
35 | }, | 61 | }, |
36 | "end": { | 62 | "end": { |
37 | "line": 24, | 63 | "line": 34, |
64 | "column": 3 | ||
65 | } | ||
66 | }, | ||
67 | { | ||
68 | "id": "pricing.trial.error", | ||
69 | "defaultMessage": "!!!Sorry, we could not activate your trial!", | ||
70 | "file": "src/components/auth/Pricing.js", | ||
71 | "start": { | ||
72 | "line": 35, | ||
73 | "column": 19 | ||
74 | }, | ||
75 | "end": { | ||
76 | "line": 38, | ||
77 | "column": 3 | ||
78 | } | ||
79 | }, | ||
80 | { | ||
81 | "id": "pricing.trial.cta.accept", | ||
82 | "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", | ||
83 | "file": "src/components/auth/Pricing.js", | ||
84 | "start": { | ||
85 | "line": 39, | ||
86 | "column": 13 | ||
87 | }, | ||
88 | "end": { | ||
89 | "line": 42, | ||
90 | "column": 3 | ||
91 | } | ||
92 | }, | ||
93 | { | ||
94 | "id": "pricing.trial.cta.skip", | ||
95 | "defaultMessage": "!!!Continue to Franz", | ||
96 | "file": "src/components/auth/Pricing.js", | ||
97 | "start": { | ||
98 | "line": 43, | ||
99 | "column": 11 | ||
100 | }, | ||
101 | "end": { | ||
102 | "line": 46, | ||
38 | "column": 3 | 103 | "column": 3 |
39 | } | 104 | } |
40 | }, | 105 | }, |
41 | { | 106 | { |
42 | "id": "pricing.link.skipPayment", | 107 | "id": "pricing.trial.features.headline", |
43 | "defaultMessage": "!!!I don't want to support the development of Franz.", | 108 | "defaultMessage": "!!!Franz Professional includes:", |
44 | "file": "src/components/auth/Pricing.js", | 109 | "file": "src/components/auth/Pricing.js", |
45 | "start": { | 110 | "start": { |
46 | "line": 25, | 111 | "line": 47, |
47 | "column": 15 | 112 | "column": 20 |
48 | }, | 113 | }, |
49 | "end": { | 114 | "end": { |
50 | "line": 28, | 115 | "line": 50, |
51 | "column": 3 | 116 | "column": 3 |
52 | } | 117 | } |
53 | } | 118 | } |
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json index 884ab0c90..eb466c0ac 100644 --- a/src/i18n/messages/src/components/services/content/Services.json +++ b/src/i18n/messages/src/components/services/content/Services.json | |||
@@ -4,11 +4,11 @@ | |||
4 | "defaultMessage": "!!!Welcome to Franz", | 4 | "defaultMessage": "!!!Welcome to Franz", |
5 | "file": "src/components/services/content/Services.js", | 5 | "file": "src/components/services/content/Services.js", |
6 | "start": { | 6 | "start": { |
7 | "line": 11, | 7 | "line": 14, |
8 | "column": 11 | 8 | "column": 11 |
9 | }, | 9 | }, |
10 | "end": { | 10 | "end": { |
11 | "line": 14, | 11 | "line": 17, |
12 | "column": 3 | 12 | "column": 3 |
13 | } | 13 | } |
14 | }, | 14 | }, |
@@ -17,11 +17,11 @@ | |||
17 | "defaultMessage": "!!!Get started", | 17 | "defaultMessage": "!!!Get started", |
18 | "file": "src/components/services/content/Services.js", | 18 | "file": "src/components/services/content/Services.js", |
19 | "start": { | 19 | "start": { |
20 | "line": 15, | 20 | "line": 18, |
21 | "column": 14 | 21 | "column": 14 |
22 | }, | 22 | }, |
23 | "end": { | 23 | "end": { |
24 | "line": 18, | 24 | "line": 21, |
25 | "column": 3 | 25 | "column": 3 |
26 | } | 26 | } |
27 | } | 27 | } |
diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json new file mode 100644 index 000000000..497e299a4 --- /dev/null +++ b/src/i18n/messages/src/components/ui/FeatureList.json | |||
@@ -0,0 +1,132 @@ | |||
1 | [ | ||
2 | { | ||
3 | "id": "pricing.features.unlimitedServices", | ||
4 | "defaultMessage": "!!!Add unlimited services", | ||
5 | "file": "src/components/ui/FeatureList.js", | ||
6 | "start": { | ||
7 | "line": 8, | ||
8 | "column": 21 | ||
9 | }, | ||
10 | "end": { | ||
11 | "line": 11, | ||
12 | "column": 3 | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | "id": "pricing.features.spellchecker", | ||
17 | "defaultMessage": "!!!Spellchecker support", | ||
18 | "file": "src/components/ui/FeatureList.js", | ||
19 | "start": { | ||
20 | "line": 12, | ||
21 | "column": 16 | ||
22 | }, | ||
23 | "end": { | ||
24 | "line": 15, | ||
25 | "column": 3 | ||
26 | } | ||
27 | }, | ||
28 | { | ||
29 | "id": "pricing.features.workspaces", | ||
30 | "defaultMessage": "!!!Workspaces", | ||
31 | "file": "src/components/ui/FeatureList.js", | ||
32 | "start": { | ||
33 | "line": 16, | ||
34 | "column": 14 | ||
35 | }, | ||
36 | "end": { | ||
37 | "line": 19, | ||
38 | "column": 3 | ||
39 | } | ||
40 | }, | ||
41 | { | ||
42 | "id": "pricing.features.customWebsites", | ||
43 | "defaultMessage": "!!!Add Custom Websites", | ||
44 | "file": "src/components/ui/FeatureList.js", | ||
45 | "start": { | ||
46 | "line": 20, | ||
47 | "column": 18 | ||
48 | }, | ||
49 | "end": { | ||
50 | "line": 23, | ||
51 | "column": 3 | ||
52 | } | ||
53 | }, | ||
54 | { | ||
55 | "id": "pricing.features.onPremise", | ||
56 | "defaultMessage": "!!!On-premise & other Hosted Services", | ||
57 | "file": "src/components/ui/FeatureList.js", | ||
58 | "start": { | ||
59 | "line": 24, | ||
60 | "column": 13 | ||
61 | }, | ||
62 | "end": { | ||
63 | "line": 27, | ||
64 | "column": 3 | ||
65 | } | ||
66 | }, | ||
67 | { | ||
68 | "id": "pricing.features.thirdPartyServices", | ||
69 | "defaultMessage": "!!!Install 3rd party services", | ||
70 | "file": "src/components/ui/FeatureList.js", | ||
71 | "start": { | ||
72 | "line": 28, | ||
73 | "column": 22 | ||
74 | }, | ||
75 | "end": { | ||
76 | "line": 31, | ||
77 | "column": 3 | ||
78 | } | ||
79 | }, | ||
80 | { | ||
81 | "id": "pricing.features.serviceProxies", | ||
82 | "defaultMessage": "!!!Service Proxies", | ||
83 | "file": "src/components/ui/FeatureList.js", | ||
84 | "start": { | ||
85 | "line": 32, | ||
86 | "column": 18 | ||
87 | }, | ||
88 | "end": { | ||
89 | "line": 35, | ||
90 | "column": 3 | ||
91 | } | ||
92 | }, | ||
93 | { | ||
94 | "id": "pricing.features.teamManagement", | ||
95 | "defaultMessage": "!!!Team Management", | ||
96 | "file": "src/components/ui/FeatureList.js", | ||
97 | "start": { | ||
98 | "line": 36, | ||
99 | "column": 18 | ||
100 | }, | ||
101 | "end": { | ||
102 | "line": 39, | ||
103 | "column": 3 | ||
104 | } | ||
105 | }, | ||
106 | { | ||
107 | "id": "pricing.features.appDelays", | ||
108 | "defaultMessage": "!!!No Waiting Screens", | ||
109 | "file": "src/components/ui/FeatureList.js", | ||
110 | "start": { | ||
111 | "line": 40, | ||
112 | "column": 13 | ||
113 | }, | ||
114 | "end": { | ||
115 | "line": 43, | ||
116 | "column": 3 | ||
117 | } | ||
118 | }, | ||
119 | { | ||
120 | "id": "pricing.features.adFree", | ||
121 | "defaultMessage": "!!!Forever ad-free", | ||
122 | "file": "src/components/ui/FeatureList.js", | ||
123 | "start": { | ||
124 | "line": 44, | ||
125 | "column": 10 | ||
126 | }, | ||
127 | "end": { | ||
128 | "line": 47, | ||
129 | "column": 3 | ||
130 | } | ||
131 | } | ||
132 | ] \ No newline at end of file | ||
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index b5423af3b..6d746254e 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -37,6 +37,8 @@ export default class UserStore extends Store { | |||
37 | 37 | ||
38 | @observable passwordRequest = new Request(this.api.user, 'password'); | 38 | @observable passwordRequest = new Request(this.api.user, 'password'); |
39 | 39 | ||
40 | @observable activateTrialRequest = new Request(this.api.user, 'activateTrial'); | ||
41 | |||
40 | @observable inviteRequest = new Request(this.api.user, 'invite'); | 42 | @observable inviteRequest = new Request(this.api.user, 'invite'); |
41 | 43 | ||
42 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); | 44 | @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); |
@@ -57,7 +59,7 @@ export default class UserStore extends Store { | |||
57 | 59 | ||
58 | @observable accountType; | 60 | @observable accountType; |
59 | 61 | ||
60 | @observable hasCompletedSignup = null; | 62 | @observable hasCompletedSignup = false; |
61 | 63 | ||
62 | @observable userData = {}; | 64 | @observable userData = {}; |
63 | 65 | ||
@@ -77,6 +79,7 @@ export default class UserStore extends Store { | |||
77 | this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); | 79 | this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); |
78 | this.actions.user.logout.listen(this._logout.bind(this)); | 80 | this.actions.user.logout.listen(this._logout.bind(this)); |
79 | this.actions.user.signup.listen(this._signup.bind(this)); | 81 | this.actions.user.signup.listen(this._signup.bind(this)); |
82 | this.actions.user.activateTrial.listen(this._activateTrial.bind(this)); | ||
80 | this.actions.user.invite.listen(this._invite.bind(this)); | 83 | this.actions.user.invite.listen(this._invite.bind(this)); |
81 | this.actions.user.update.listen(this._update.bind(this)); | 84 | this.actions.user.update.listen(this._update.bind(this)); |
82 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); | 85 | this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); |
@@ -199,6 +202,20 @@ export default class UserStore extends Store { | |||
199 | gaEvent('User', 'retrievePassword'); | 202 | gaEvent('User', 'retrievePassword'); |
200 | } | 203 | } |
201 | 204 | ||
205 | @action async _activateTrial({ planId }) { | ||
206 | debug('activate trial', planId); | ||
207 | |||
208 | this.activateTrialRequest.execute({ | ||
209 | plan: planId, | ||
210 | }); | ||
211 | |||
212 | await this.activateTrialRequest._promise; | ||
213 | |||
214 | this.stores.features.featuresRequest.invalidate({ immediately: true }); | ||
215 | |||
216 | gaEvent('User', 'activateTrial'); | ||
217 | } | ||
218 | |||
202 | @action async _invite({ invites }) { | 219 | @action async _invite({ invites }) { |
203 | const data = invites.filter(invite => invite.email !== ''); | 220 | const data = invites.filter(invite => invite.email !== ''); |
204 | 221 | ||
diff --git a/src/styles/auth.scss b/src/styles/auth.scss index 0a075036a..154a71a36 100644 --- a/src/styles/auth.scss +++ b/src/styles/auth.scss | |||
@@ -9,7 +9,7 @@ | |||
9 | } | 9 | } |
10 | 10 | ||
11 | .auth__logo.auth__logo--sm { | 11 | .auth__logo.auth__logo--sm { |
12 | border: 4px solid $dark-theme-black; | 12 | border: none; |
13 | box-shadow: 0 0 6px rgba($dark-theme-black, .5); | 13 | box-shadow: 0 0 6px rgba($dark-theme-black, .5); |
14 | } | 14 | } |
15 | 15 | ||
diff --git a/src/styles/reset.scss b/src/styles/reset.scss index f46ede4a2..d87ce652a 100644 --- a/src/styles/reset.scss +++ b/src/styles/reset.scss | |||
@@ -51,7 +51,6 @@ button { | |||
51 | padding: 0; | 51 | padding: 0; |
52 | 52 | ||
53 | &:focus { outline: 0; } | 53 | &:focus { outline: 0; } |
54 | .theme__dark & { color: $dark-theme-gray-smoke; } | ||
55 | } | 54 | } |
56 | 55 | ||
57 | html { | 56 | html { |