diff options
author | 2018-01-19 10:05:51 +0100 | |
---|---|---|
committer | 2018-01-19 10:05:51 +0100 | |
commit | 7c8782e2a1db34efa8b4ae8160c1041d71448432 (patch) | |
tree | 20e6f2158c28d8993f2967c8920ba5c53ac954f7 /src/components/subscription | |
parent | fix property change due to mobx-react-form update (diff) | |
download | ferdium-app-7c8782e2a1db34efa8b4ae8160c1041d71448432.tar.gz ferdium-app-7c8782e2a1db34efa8b4ae8160c1041d71448432.tar.zst ferdium-app-7c8782e2a1db34efa8b4ae8160c1041d71448432.zip |
change directory structure for subscription
move to own sub-folders `subscription`
Diffstat (limited to 'src/components/subscription')
-rw-r--r-- | src/components/subscription/SubscriptionForm.js | 220 | ||||
-rw-r--r-- | src/components/subscription/SubscriptionPopup.js | 83 |
2 files changed, 303 insertions, 0 deletions
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js new file mode 100644 index 000000000..dd350479d --- /dev/null +++ b/src/components/subscription/SubscriptionForm.js | |||
@@ -0,0 +1,220 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import Form from '../../lib/Form'; | ||
7 | import Radio from '../ui/Radio'; | ||
8 | import Button from '../ui/Button'; | ||
9 | import Loader from '../ui/Loader'; | ||
10 | |||
11 | import { required } from '../../helpers/validation-helpers'; | ||
12 | |||
13 | const messages = defineMessages({ | ||
14 | submitButtonLabel: { | ||
15 | id: 'subscription.submit.label', | ||
16 | defaultMessage: '!!!Support the development of Franz', | ||
17 | }, | ||
18 | paymentSessionError: { | ||
19 | id: 'subscription.paymentSessionError', | ||
20 | defaultMessage: '!!!Could not initialize payment form', | ||
21 | }, | ||
22 | typeFree: { | ||
23 | id: 'subscription.type.free', | ||
24 | defaultMessage: '!!!free', | ||
25 | }, | ||
26 | typeMonthly: { | ||
27 | id: 'subscription.type.month', | ||
28 | defaultMessage: '!!!month', | ||
29 | }, | ||
30 | typeYearly: { | ||
31 | id: 'subscription.type.year', | ||
32 | defaultMessage: '!!!year', | ||
33 | }, | ||
34 | includedFeatures: { | ||
35 | id: 'subscription.includedFeatures', | ||
36 | defaultMessage: '!!!The Franz Premium Supporter Account includes', | ||
37 | }, | ||
38 | features: { | ||
39 | unlimitedServices: { | ||
40 | id: 'subscription.features.unlimitedServices', | ||
41 | defaultMessage: '!!!Add unlimited services', | ||
42 | }, | ||
43 | onpremise: { | ||
44 | id: 'subscription.features.onpremise', | ||
45 | defaultMessage: '!!!Add on-premise/hosted services like HipChat', | ||
46 | }, | ||
47 | customServices: { | ||
48 | id: 'subscription.features.customServices', | ||
49 | defaultMessage: '!!!Add your custom services', | ||
50 | }, | ||
51 | encryptedSync: { | ||
52 | id: 'subscription.features.encryptedSync', | ||
53 | defaultMessage: '!!!Encrypted session synchronization', | ||
54 | }, | ||
55 | vpn: { | ||
56 | id: 'subscription.features.vpn', | ||
57 | defaultMessage: '!!!Proxy & VPN support', | ||
58 | }, | ||
59 | ads: { | ||
60 | id: 'subscription.features.ads', | ||
61 | defaultMessage: '!!!No ads, ever!', | ||
62 | }, | ||
63 | comingSoon: { | ||
64 | id: 'subscription.features.comingSoon', | ||
65 | defaultMessage: '!!!coming soon', | ||
66 | }, | ||
67 | }, | ||
68 | euTaxInfo: { | ||
69 | id: 'subscription.euTaxInfo', | ||
70 | defaultMessage: '!!!EU residents: local sales tax may apply', | ||
71 | }, | ||
72 | }); | ||
73 | |||
74 | @observer | ||
75 | export default class SubscriptionForm extends Component { | ||
76 | static propTypes = { | ||
77 | plan: MobxPropTypes.objectOrObservableObject.isRequired, | ||
78 | isLoading: PropTypes.bool.isRequired, | ||
79 | handlePayment: PropTypes.func.isRequired, | ||
80 | retryPlanRequest: PropTypes.func.isRequired, | ||
81 | isCreatingHostedPage: PropTypes.bool.isRequired, | ||
82 | error: PropTypes.bool.isRequired, | ||
83 | showSkipOption: PropTypes.bool, | ||
84 | skipAction: PropTypes.func, | ||
85 | skipButtonLabel: PropTypes.string, | ||
86 | hideInfo: PropTypes.bool.isRequired, | ||
87 | }; | ||
88 | |||
89 | static defaultProps ={ | ||
90 | content: '', | ||
91 | showSkipOption: false, | ||
92 | skipAction: () => null, | ||
93 | skipButtonLabel: '', | ||
94 | } | ||
95 | |||
96 | static contextTypes = { | ||
97 | intl: intlShape, | ||
98 | }; | ||
99 | |||
100 | componentWillMount() { | ||
101 | this.form = this.prepareForm(); | ||
102 | } | ||
103 | |||
104 | prepareForm() { | ||
105 | const { intl } = this.context; | ||
106 | |||
107 | const form = { | ||
108 | fields: { | ||
109 | paymentTier: { | ||
110 | value: 'year', | ||
111 | validators: [required], | ||
112 | options: [{ | ||
113 | value: 'month', | ||
114 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month') | ||
115 | ? `${this.props.plan.month.price} / ${intl.formatMessage(messages.typeMonthly)}` | ||
116 | : 'monthly'}`, | ||
117 | }, { | ||
118 | value: 'year', | ||
119 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year') | ||
120 | ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}` | ||
121 | : 'yearly'}`, | ||
122 | }], | ||
123 | }, | ||
124 | }, | ||
125 | }; | ||
126 | |||
127 | if (this.props.showSkipOption) { | ||
128 | form.fields.paymentTier.options.unshift({ | ||
129 | value: 'skip', | ||
130 | label: `€ 0 / ${intl.formatMessage(messages.typeFree)}`, | ||
131 | }); | ||
132 | } | ||
133 | |||
134 | return new Form(form, this.context.intl); | ||
135 | } | ||
136 | |||
137 | render() { | ||
138 | const { | ||
139 | isLoading, | ||
140 | isCreatingHostedPage, | ||
141 | handlePayment, | ||
142 | retryPlanRequest, | ||
143 | error, | ||
144 | showSkipOption, | ||
145 | skipAction, | ||
146 | skipButtonLabel, | ||
147 | hideInfo, | ||
148 | } = this.props; | ||
149 | const { intl } = this.context; | ||
150 | |||
151 | if (error) { | ||
152 | return ( | ||
153 | <Button | ||
154 | label="Reload" | ||
155 | onClick={retryPlanRequest} | ||
156 | isLoaded={!isLoading} | ||
157 | /> | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | return ( | ||
162 | <Loader loaded={!isLoading}> | ||
163 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> | ||
164 | {!hideInfo && ( | ||
165 | <div className="subscription__premium-info"> | ||
166 | <div> | ||
167 | <p> | ||
168 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> | ||
169 | </p> | ||
170 | <div className="subscription"> | ||
171 | <ul className="subscription__premium-features"> | ||
172 | <li>{intl.formatMessage(messages.features.onpremise)}</li> | ||
173 | <li> | ||
174 | {intl.formatMessage(messages.features.encryptedSync)} | ||
175 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | ||
176 | </li> | ||
177 | <li> | ||
178 | {intl.formatMessage(messages.features.customServices)} | ||
179 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | ||
180 | </li> | ||
181 | <li> | ||
182 | {intl.formatMessage(messages.features.vpn)} | ||
183 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | ||
184 | </li> | ||
185 | <li> | ||
186 | {intl.formatMessage(messages.features.ads)} | ||
187 | </li> | ||
188 | </ul> | ||
189 | </div> | ||
190 | </div> | ||
191 | </div> | ||
192 | )} | ||
193 | <div> | ||
194 | {error.code === 'no-payment-session' && ( | ||
195 | <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p> | ||
196 | )} | ||
197 | </div> | ||
198 | {showSkipOption && this.form.$('paymentTier').value === 'skip' ? ( | ||
199 | <Button | ||
200 | label={skipButtonLabel} | ||
201 | className="auth__button" | ||
202 | onClick={skipAction} | ||
203 | /> | ||
204 | ) : ( | ||
205 | <Button | ||
206 | label={intl.formatMessage(messages.submitButtonLabel)} | ||
207 | className="auth__button" | ||
208 | loaded={!isCreatingHostedPage} | ||
209 | onClick={() => handlePayment(this.form.$('paymentTier').value)} | ||
210 | /> | ||
211 | )} | ||
212 | {this.form.$('paymentTier').value !== 'skip' && ( | ||
213 | <p className="legal"> | ||
214 | {intl.formatMessage(messages.euTaxInfo)} | ||
215 | </p> | ||
216 | )} | ||
217 | </Loader> | ||
218 | ); | ||
219 | } | ||
220 | } | ||
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js new file mode 100644 index 000000000..528d02907 --- /dev/null +++ b/src/components/subscription/SubscriptionPopup.js | |||
@@ -0,0 +1,83 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import Webview from 'react-electron-web-view'; | ||
6 | |||
7 | import Button from '../ui/Button'; | ||
8 | |||
9 | const messages = defineMessages({ | ||
10 | buttonCancel: { | ||
11 | id: 'subscriptionPopup.buttonCancel', | ||
12 | defaultMessage: '!!!Cancel', | ||
13 | }, | ||
14 | buttonDone: { | ||
15 | id: 'subscriptionPopup.buttonDone', | ||
16 | defaultMessage: '!!!Done', | ||
17 | }, | ||
18 | }); | ||
19 | |||
20 | @observer | ||
21 | export default class SubscriptionPopup extends Component { | ||
22 | static propTypes = { | ||
23 | url: PropTypes.string.isRequired, | ||
24 | closeWindow: PropTypes.func.isRequired, | ||
25 | completeCheck: PropTypes.func.isRequired, | ||
26 | isCompleted: PropTypes.bool.isRequired, | ||
27 | }; | ||
28 | |||
29 | static contextTypes = { | ||
30 | intl: intlShape, | ||
31 | }; | ||
32 | |||
33 | state = { | ||
34 | isFakeLoading: false, | ||
35 | }; | ||
36 | |||
37 | // We delay the window closing a bit in order to give | ||
38 | // the Recurly webhook a few seconds to do it's magic | ||
39 | delayedCloseWindow() { | ||
40 | this.setState({ | ||
41 | isFakeLoading: true, | ||
42 | }); | ||
43 | |||
44 | setTimeout(() => { | ||
45 | this.props.closeWindow(); | ||
46 | }, 4000); | ||
47 | } | ||
48 | |||
49 | render() { | ||
50 | const { url, closeWindow, completeCheck, isCompleted } = this.props; | ||
51 | const { intl } = this.context; | ||
52 | |||
53 | return ( | ||
54 | <div className="subscription-popup"> | ||
55 | <div className="subscription-popup__content"> | ||
56 | <Webview | ||
57 | className="subscription-popup__webview" | ||
58 | |||
59 | autosize | ||
60 | src={encodeURI(url)} | ||
61 | onDidNavigate={completeCheck} | ||
62 | // onNewWindow={(event, url, frameName, options) => | ||
63 | // openWindow({ event, url, frameName, options })} | ||
64 | /> | ||
65 | </div> | ||
66 | <div className="subscription-popup__toolbar franz-form"> | ||
67 | <Button | ||
68 | label={intl.formatMessage(messages.buttonCancel)} | ||
69 | buttonType="secondary" | ||
70 | onClick={closeWindow} | ||
71 | disabled={isCompleted} | ||
72 | /> | ||
73 | <Button | ||
74 | label={intl.formatMessage(messages.buttonDone)} | ||
75 | onClick={() => this.delayedCloseWindow()} | ||
76 | disabled={!isCompleted} | ||
77 | loaded={!this.state.isFakeLoading} | ||
78 | /> | ||
79 | </div> | ||
80 | </div> | ||
81 | ); | ||
82 | } | ||
83 | } | ||