hippo-fw 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/client/hippo/access/subscription-choice-layer.jsx +170 -0
  3. data/client/hippo/access/subscription-choice-layer/cancel-subscription.jsx +111 -0
  4. data/client/hippo/access/subscription-choice-layer/payment-form.jsx +154 -0
  5. data/client/hippo/access/subscription-choice-layer/subscription-choice.scss +29 -0
  6. data/client/hippo/boot.jsx +1 -1
  7. data/client/hippo/components/asset.jsx +1 -1
  8. data/client/hippo/components/asset.scss +1 -0
  9. data/client/hippo/components/data-table.jsx +36 -38
  10. data/client/hippo/components/date-time.jsx +25 -9
  11. data/client/hippo/components/form/api.js +1 -0
  12. data/client/hippo/components/form/fields.jsx +3 -2
  13. data/client/hippo/components/form/fields/checkbox-wrapper.jsx +1 -1
  14. data/client/hippo/components/form/fields/date-wrapper.jsx +1 -1
  15. data/client/hippo/components/form/fields/email-wrapper.jsx +31 -0
  16. data/client/hippo/components/form/fields/form-field.scss +8 -0
  17. data/client/hippo/components/form/fields/label.jsx +5 -7
  18. data/client/hippo/components/form/fields/select-wrapper.jsx +1 -1
  19. data/client/hippo/components/form/fields/tags-wrapper.jsx +1 -1
  20. data/client/hippo/components/form/fields/text-wrapper.jsx +1 -1
  21. data/client/hippo/components/form/fields/textarea-wrapper.jsx +1 -1
  22. data/client/hippo/components/form/wrapper.jsx +1 -1
  23. data/client/hippo/components/grid.js +1 -0
  24. data/client/hippo/components/master-detail.jsx +1 -1
  25. data/client/hippo/components/network-activity-overlay.jsx +6 -6
  26. data/client/hippo/components/payments/field.jsx +64 -0
  27. data/client/hippo/components/payments/field.scss +18 -0
  28. data/client/hippo/components/popout-window.jsx +1 -1
  29. data/client/hippo/components/query-builder.jsx +1 -1
  30. data/client/hippo/components/query-builder/boolean-picker.jsx +1 -1
  31. data/client/hippo/components/query-builder/clause-filter.jsx +2 -2
  32. data/client/hippo/components/query-builder/clause.jsx +16 -10
  33. data/client/hippo/components/query-builder/date-picker.jsx +1 -1
  34. data/client/hippo/components/query-builder/query-builder.scss +23 -2
  35. data/client/hippo/components/record-finder.jsx +5 -2
  36. data/client/hippo/components/record-finder/query-layer.jsx +1 -1
  37. data/client/hippo/components/save-button.jsx +1 -1
  38. data/client/hippo/components/screen.jsx +1 -1
  39. data/client/hippo/components/text-editor.jsx +82 -40
  40. data/client/hippo/components/text-editor/renderer.jsx +15 -35
  41. data/client/hippo/components/text-editor/renderer.scss +15 -0
  42. data/client/hippo/components/text-editor/text-editor.scss +2 -15
  43. data/client/hippo/components/text-editor/upload-adapter.js +66 -0
  44. data/client/hippo/components/time-zone-select.jsx +1 -1
  45. data/client/hippo/components/tool-tip.jsx +9 -14
  46. data/client/hippo/components/toolbar.jsx +16 -0
  47. data/client/hippo/components/warning-notification.jsx +1 -1
  48. data/client/hippo/extensions/base.js +3 -2
  49. data/client/hippo/extensions/index.js +1 -1
  50. data/client/hippo/lib/action_cable.js +8 -0
  51. data/client/hippo/lib/action_cable/cable.js +47 -0
  52. data/client/hippo/lib/action_cable/connection.js +192 -0
  53. data/client/hippo/lib/action_cable/connection_monitor.js +135 -0
  54. data/client/hippo/lib/action_cable/consumer.js +56 -0
  55. data/client/hippo/lib/action_cable/subscription.js +98 -0
  56. data/client/hippo/lib/action_cable/subscriptions.js +129 -0
  57. data/client/hippo/lib/date-range.js +22 -5
  58. data/client/hippo/lib/lazy-getter.js +31 -0
  59. data/client/hippo/lib/util.js +6 -1
  60. data/client/hippo/models/base.js +8 -3
  61. data/client/hippo/models/config.js +7 -2
  62. data/client/hippo/models/date-type.js +14 -0
  63. data/client/hippo/models/decorators.js +5 -5
  64. data/client/hippo/models/pub_sub.js +1 -1
  65. data/client/hippo/models/query/array-result.js +4 -1
  66. data/client/hippo/models/subscription.js +35 -0
  67. data/client/hippo/models/sync.js +1 -1
  68. data/client/hippo/models/tenant.js +14 -1
  69. data/client/hippo/react/component-not-found.jsx +14 -18
  70. data/client/hippo/screens/async-loading.jsx +46 -0
  71. data/client/hippo/screens/definition.js +2 -1
  72. data/client/hippo/screens/preferences.jsx +57 -0
  73. data/client/hippo/screens/system-settings.jsx +4 -6
  74. data/client/hippo/screens/system-settings/mailer-config.jsx +1 -1
  75. data/client/hippo/screens/system-settings/tenant.jsx +57 -4
  76. data/client/hippo/screens/user-management.jsx +2 -2
  77. data/client/hippo/screens/user-management/edit-form.jsx +1 -1
  78. data/client/hippo/styles/global/fancy-header.scss +2 -1
  79. data/client/hippo/styles/global/mixins.scss +14 -1
  80. data/client/hippo/testing/index.js +7 -0
  81. data/client/hippo/user.js +9 -2
  82. data/client/hippo/workspace/index.jsx +29 -8
  83. data/client/hippo/workspace/menu-group.jsx +1 -1
  84. data/client/hippo/workspace/menu-option.jsx +2 -0
  85. data/client/hippo/workspace/menu.jsx +30 -6
  86. data/client/hippo/workspace/screen.jsx +5 -1
  87. data/client/hippo/workspace/styles.scss +22 -3
  88. data/command-reference-files/initial/Gemfile +1 -1
  89. data/config/routes.rb +6 -0
  90. data/config/screens.rb +9 -17
  91. data/db/migrate/20171129024737_create_subscriptions.rb +12 -0
  92. data/fixtures/vcr_cassettes/Tenant_changes/sends_email_when_tenant_identifier_changes.yml +72 -0
  93. data/fixtures/vcr_cassettes/Tenant_isoloation/disallows_using_a_user_s_token_on_incorrect_domain.yml +141 -0
  94. data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_bar_s_tenant_data_from_foo.yml +141 -0
  95. data/fixtures/vcr_cassettes/Tenant_isoloation/isolates_foo_s_tenant_data_from_bar.yml +141 -0
  96. data/hippo-fw.gemspec +4 -3
  97. data/lib/hippo.rb +1 -0
  98. data/lib/hippo/access/roles/basic_user.rb +1 -0
  99. data/lib/hippo/api/authentication_provider.rb +4 -5
  100. data/lib/hippo/api/handlers/asset.rb +9 -4
  101. data/lib/hippo/api/handlers/subscription.rb +39 -0
  102. data/lib/hippo/api/handlers/user_session.rb +0 -1
  103. data/lib/hippo/api/helper_methods.rb +8 -4
  104. data/lib/hippo/api/request_wrapper.rb +12 -1
  105. data/lib/hippo/command/console.rb +3 -3
  106. data/lib/hippo/concerns/asset_uploader.rb +8 -2
  107. data/lib/hippo/concerns/pub_sub.rb +8 -8
  108. data/lib/hippo/configuration.rb +6 -4
  109. data/lib/hippo/extension.rb +3 -2
  110. data/lib/hippo/model.rb +1 -0
  111. data/lib/hippo/models/subscription.rb +7 -0
  112. data/lib/hippo/payments.rb +129 -0
  113. data/lib/hippo/screen.rb +4 -2
  114. data/lib/hippo/screen/definition.rb +4 -0
  115. data/lib/hippo/spec_helper.rb +4 -4
  116. data/lib/hippo/templates/liquid/precision.rb +9 -0
  117. data/lib/hippo/tenant.rb +4 -5
  118. data/lib/hippo/user.rb +5 -5
  119. data/lib/hippo/version.rb +1 -1
  120. data/lib/hippo/webpack.rb +5 -1
  121. data/package-lock.json +437 -881
  122. data/package.json +19 -5
  123. data/spec/client/components/__snapshots__/query-builder.spec.jsx.snap +34 -1
  124. data/spec/client/components/__snapshots__/record-finder.spec.jsx.snap +1 -0
  125. data/spec/client/models/base.spec.js +7 -0
  126. data/spec/client/models/subscription.spec.js +8 -0
  127. data/spec/client/screens/__snapshots__/preferences.spec.jsx.snap +223 -0
  128. data/spec/client/screens/preferences.spec.jsx +10 -0
  129. data/spec/client/test-models.js +1 -1
  130. data/spec/client/workspace/__snapshots__/menu.spec.jsx.snap +12 -0
  131. data/spec/factories/subscription.rb +6 -0
  132. data/spec/factories/tenant.rb +1 -1
  133. data/spec/factories/user.rb +1 -1
  134. data/spec/server/api/tenant_change_spec.rb +11 -8
  135. data/spec/server/api/tenant_isolation_spec.rb +11 -8
  136. data/spec/server/api/user_sessions_spec.rb +10 -7
  137. data/spec/server/api/user_spec.rb +45 -0
  138. data/spec/server/models/subscription_spec.rb +10 -0
  139. data/spec/server/payment_helpers.rb +13 -0
  140. data/spec/server/print/form_spec.rb +1 -1
  141. data/spec/server/spec_helper.rb +3 -11
  142. data/templates/js/screen-definitions.js +4 -1
  143. data/templates/spec/factories/model.rb +1 -1
  144. data/views/hippo_root_view.erb +5 -5
  145. metadata +84 -25
  146. data/client/hippo/components/grid/config.json +0 -3
  147. data/client/hippo/components/grid/editors.scss +0 -78
  148. data/client/hippo/components/grid/index.js +0 -2
  149. data/client/hippo/components/grid/row-editor.scss +0 -74
  150. data/client/hippo/components/grid/styles.scss +0 -118
  151. data/client/hippo/components/text-editor/display-modes/Button.jsx +0 -20
  152. data/client/hippo/components/text-editor/display-modes/ToggleEdit.jsx +0 -23
  153. data/client/hippo/components/text-editor/display-modes/ToggleInsert.jsx +0 -22
  154. data/client/hippo/components/text-editor/display-modes/ToggleLayout.jsx +0 -22
  155. data/client/hippo/components/text-editor/display-modes/TogglePreview.jsx +0 -22
  156. data/client/hippo/components/text-editor/display-modes/ToggleResize.jsx +0 -22
  157. data/client/hippo/components/text-editor/display-modes/index.css +0 -0
  158. data/client/hippo/components/text-editor/display-modes/index.js +0 -30
  159. data/client/hippo/components/text-editor/image-plugin/Component/Display/index.js +0 -82
  160. data/client/hippo/components/text-editor/image-plugin/Component/Form/index.js +0 -42
  161. data/client/hippo/components/text-editor/image-plugin/Component/index.js +0 -16
  162. data/client/hippo/components/text-editor/image-plugin/Component/index.scss +0 -0
  163. data/client/hippo/components/text-editor/image-plugin/index.js +0 -32
  164. data/client/hippo/components/text-editor/image-plugin/index.scss +0 -25
  165. data/client/hippo/components/toolbar/changes-notification.scss +0 -63
  166. data/client/hippo/components/toolbar/index.js +0 -3
  167. data/client/hippo/components/toolbar/styles.scss +0 -74
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e97da3fa2dce1988dbd6c99f57b23fa97c107804
4
- data.tar.gz: 6ddec0b54d142580acde726ad71285a0bd418a60
3
+ metadata.gz: 597e5e5e442b8cd8542db1feee9d6d1e63641b97
4
+ data.tar.gz: a93815487e35b9d64d9bd50e7b023cd6c3934bfc
5
5
  SHA512:
6
- metadata.gz: bb9884ee011d186fb0465284b8e537507d64d806b60be2ee687a6c52f16555168640734dd1f87b727f61e7dccf5ea1df349e95c904c8b39e1c388ba87de013d8
7
- data.tar.gz: 65bad2b85ca2f0d47ec885120dbf7dd73324f3f75fd3d67177ba7da1207684f39d3b65f51750c175900678059d1415f3b6112f75d41a0f23adc5cb8b943479d3
6
+ metadata.gz: 9d8d66bdad04271e665bcca7e864dc40a4cb72dc734b9eec26e3903db4a0b1b4ca22a5042cb2e6524e613d6a4eaa381dee94a7b563b7731b873b745b81a76b08
7
+ data.tar.gz: 38b2624aada6de1c152d4dc180533e744e9c4688e33c8011b68cb1057b96862efb5a007a4cd1c2501b1eb027628d959a89df2dab86e6902c522cadcf4cba241f
@@ -0,0 +1,170 @@
1
+ import React from 'react';
2
+ import { action, observable, computed } from 'mobx';
3
+ import { observer } from 'mobx-react';
4
+ import { sortBy } from 'lodash';
5
+ import SwipeableViews from 'react-swipeable-views';
6
+ import Box from 'grommet/components/Box';
7
+ import Button from 'grommet/components/Button';
8
+ import NextIcon from 'grommet/components/icons/base/Next';
9
+ import CloseIcon from 'grommet/components/icons/base/Close';
10
+ import StarIcon from 'grommet/components/icons/base/Star';
11
+ import Layer from 'grommet/components/Layer';
12
+ import Tenant from '../models/tenant';
13
+ import Subscription from '../models/subscription';
14
+ import PaymentForm from './subscription-choice-layer/payment-form';
15
+ import './subscription-choice-layer/subscription-choice.scss';
16
+
17
+ @observer
18
+ export default class SubscriptionChoiceLayer extends React.Component {
19
+
20
+ @observable subscriptionId = Tenant.current.subscription_id;
21
+ @observable displayIndex = 0;
22
+ @observable showSubscription = false;
23
+ @observable subscription;
24
+
25
+ @action.bound onSubscriptionSelect(ev) {
26
+ this.subscriptionId = ev.target.id;
27
+ }
28
+
29
+ @computed get sortedSubscriptions() {
30
+ return sortBy(Subscription.all, pl => parseFloat(pl.price));
31
+ }
32
+
33
+ @action.bound showCCForm(subscription) {
34
+ this.displayIndex = 1;
35
+ this.subscription = subscription;
36
+ }
37
+
38
+ @action.bound goToSubscriptions() {
39
+ this.displayIndex = 0;
40
+ this.subscription = null;
41
+ }
42
+
43
+ @action.bound onPaymentSuccess() {
44
+ this.displayIndex = 2;
45
+ }
46
+
47
+ @action.bound setSubscriptionOnTenant() {
48
+ Tenant.current.subscription_id = this.subscription.id;
49
+ if (this.props.onCancel) { this.props.onCancel(); }
50
+ }
51
+
52
+ renderSubscriptionChangeSuccess() {
53
+ if (this.displayIndex !== 2) { return <span />; }
54
+ const { subscription } = this;
55
+ return (
56
+ <Box
57
+ className="success"
58
+ margin="large"
59
+ >
60
+ <h4>You've selected the “{subscription.name}” subscription</h4>
61
+ <p>
62
+ You can cancel the subscription or select a different
63
+ one at any time from “System Settings”.
64
+ </p>
65
+ <Button
66
+ label="Got it"
67
+ onClick={this.setSubscriptionOnTenant}
68
+ />
69
+ </Box>
70
+ );
71
+ }
72
+
73
+ renderCardFields() {
74
+ if (this.displayIndex !== 1) { return <span />; }
75
+ const { subscription } = this;
76
+ return (
77
+ <PaymentForm
78
+ subscription={subscription}
79
+ onCancel={this.goToSubscriptions}
80
+ onSuccess={this.onPaymentSuccess}
81
+ />
82
+ );
83
+ }
84
+
85
+ @action.bound renderSubscriptionChoice(subscription) {
86
+ const isCurrent = (Tenant.current.subscription === subscription);
87
+ return (
88
+ <Box
89
+ key={subscription.id}
90
+ className="subscription"
91
+ margin={{ bottom: 'medium' }}
92
+ onClick={() => this.showCCForm(subscription)}
93
+ >
94
+ <Box
95
+ direction="row"
96
+ align="center"
97
+ >
98
+ <Box flex>
99
+ <Box direction="row" justify="between">
100
+ <span>
101
+ {subscription.name}
102
+ {isCurrent && <StarIcon colorIndex="brand" size="small" />}
103
+ </span>
104
+
105
+ <span className="price">
106
+ $ {subscription.formattedPrice}/month
107
+ </span>
108
+ </Box>
109
+ <span>{subscription.description}</span>
110
+ </Box>
111
+ <NextIcon className="select"/>
112
+ </Box>
113
+ </Box>
114
+ );
115
+ }
116
+
117
+ renderCloseButton() {
118
+ if (!this.props.onCancel) { return null; }
119
+
120
+ return (
121
+ <Box margin={{ bottom: 'medium' }} align="end">
122
+ <Button
123
+ label="Close"
124
+ icon={<CloseIcon />}
125
+ onClick={this.props.onCancel}
126
+ />
127
+ </Box>
128
+ );
129
+ }
130
+
131
+ renderSubscriptionListing() {
132
+ if (this.props.isCanceling) {
133
+ return <CancelSubscription onCancel={this.props.onCancel} />;
134
+ }
135
+
136
+ return (
137
+ <Box
138
+ className="subscriptions-listing"
139
+ justify="around"
140
+ >
141
+ <h3>Choose subscription</h3>
142
+ {this.sortedSubscriptions.map(this.renderSubscriptionChoice)}
143
+ {this.renderCloseButton()}
144
+ </Box>
145
+ );
146
+ }
147
+
148
+ render() {
149
+ return (
150
+ <Layer
151
+ className="subscription-choice-layer"
152
+ closer={!!this.props.onCancel}
153
+ onClose={this.props.onCancel}
154
+ >
155
+ <Box
156
+ margin={{ vertical: 'medium' }}
157
+ >
158
+ <SwipeableViews
159
+ disabled index={this.displayIndex}
160
+ >
161
+ {this.renderSubscriptionListing()}
162
+ {this.renderCardFields()}
163
+ {this.renderSubscriptionChangeSuccess()}
164
+ </SwipeableViews>
165
+ </Box>
166
+ </Layer>
167
+ );
168
+ }
169
+
170
+ }
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { defer } from 'lodash';
3
+ import { action, observable } from 'mobx';
4
+ import { observer } from 'mobx-react';
5
+ import Button from 'grommet/components/Button';
6
+ import ClearIcon from 'grommet/components/icons/base/Clear';
7
+ import CloseIcon from 'grommet/components/icons/base/Close';
8
+ import Box from 'grommet/components/Box';
9
+ import Config from '../../config';
10
+ import User from '../../user';
11
+ import Tenant from '../../models/tenant';
12
+ import NetworkActivityOverlay from '../../components/network-activity-overlay';
13
+
14
+ @observer
15
+ export default class CancelSubscription extends React.Component {
16
+
17
+ @observable isCanceled = false;
18
+ @observable isCanceling = false;
19
+
20
+ @action.bound cancelSubscription() {
21
+ this.isCanceling = true;
22
+ Tenant.current.subscription.destroy().then(() => {
23
+ this.isCanceling = false;
24
+ this.isCanceled = true;
25
+ });
26
+ }
27
+
28
+ @action.bound onCancelComplete() {
29
+ this.props.onCancel();
30
+ User.logout();
31
+ defer(() => { Tenant.current.subscription_id = null; });
32
+ }
33
+
34
+ renderCanceled() {
35
+ window.c = Config;
36
+ return (
37
+ <Box className="subscription-canceled">
38
+ <h3>Subscription canceled</h3>
39
+ <h5>
40
+ We're sorry to see you go! If you'd ever like to contact us and
41
+ share any feedback we can be reached
42
+ at <a href={`mailto:${Config.support_email}`}>{Config.support_email}</a>
43
+ </h5>
44
+ <p>
45
+ Your subscription to {Config.product_name} has been canceled.
46
+ You can regain access to your account information by
47
+ logging in and choosing a new plan.
48
+ </p>
49
+ <Box align="end">
50
+ <Button
51
+ critical
52
+ icon={<ClearIcon />}
53
+ onClick={this.onCancelComplete}
54
+ label="Logout"
55
+ />
56
+ </Box>
57
+ </Box>
58
+ );
59
+ }
60
+
61
+ render() {
62
+ if (this.isCanceled) { return this.renderCanceled(); }
63
+
64
+ return (
65
+ <Box
66
+ className="subscription-cancel"
67
+ justify="around"
68
+ size="large"
69
+ >
70
+ <NetworkActivityOverlay
71
+ message="Canceling…"
72
+ model={Tenant.current.subscription}
73
+ visible={this.isCanceling}
74
+ />
75
+ <h3>Cancel subscription?</h3>
76
+ <h5>
77
+ We're sorry to see you go! If you'd ever like to contact us and
78
+ share any feedback we can be reached
79
+ at <a href={`mailto:${Config.support_email}`}>{Config.support_email}</a>
80
+ </h5>
81
+ <p>
82
+ Canceling your subscription will immediately revoke
83
+ your access to {Config.product_name}, and will suspend
84
+ all of your current listings.
85
+ </p>
86
+ <p>
87
+ Your data will remain intact and can be accessed once you
88
+ re-subscribe to a plan.
89
+ </p>
90
+ <Box
91
+ justify="between"
92
+ direction="row"
93
+ pad={{ between: 'small' }}
94
+ >
95
+ <Button
96
+ critical
97
+ icon={<ClearIcon />}
98
+ onClick={this.cancelSubscription}
99
+ label="Cancel Subscription"
100
+ />
101
+ <Button
102
+ label="Close"
103
+ icon={<CloseIcon />}
104
+ onClick={this.props.onCancel}
105
+ />
106
+ </Box>
107
+ </Box>
108
+ );
109
+ }
110
+
111
+ }
@@ -0,0 +1,154 @@
1
+ import React from 'react';
2
+ import { each, every } from 'lodash';
3
+ import { action, observable, computed } from 'mobx';
4
+ import { observer } from 'mobx-react';
5
+ import PaymentFields from 'payment-fields';
6
+ import Button from 'grommet/components/Button';
7
+ import CreditCardIcon from 'grommet/components/icons/base/CreditCard';
8
+ import Box from 'grommet/components/Box';
9
+ import NetworkActivityOverlay from '../../components/network-activity-overlay';
10
+ import CardField from '../../components/payments/field';
11
+ import WarningNotification from '../../components/warning-notification';
12
+
13
+ @observer
14
+ export default class PaymentForm extends React.Component {
15
+
16
+ @observable isTokenizing = false;
17
+ @observable getToken;
18
+ @observable fields = {
19
+ postalCode: false,
20
+ cardNumber: false,
21
+ expirationDate: false,
22
+ cvv: false,
23
+ }
24
+
25
+ componentWillMount() {
26
+ this.props.subscription.fetch();
27
+ }
28
+
29
+ @action.bound setFieldRef(ref) {
30
+ if (ref) {
31
+ this.fields[ref.props.type] = ref;
32
+ }
33
+ }
34
+
35
+ @action.bound onSubscribe() {
36
+ if (!every(this.fields, 'isValid')) {
37
+ each(this.fields, f => f.exposeError());
38
+ return;
39
+ }
40
+ this.subscribeToSubscription();
41
+ }
42
+
43
+ @action.bound onFormReady(ev) {
44
+ this.getToken = ev.tokenize;
45
+ }
46
+
47
+ @action.bound subscribeToSubscription() {
48
+ this.isTokenizing = true;
49
+ const { subscription } = this.props;
50
+ this.getToken().then(({ token: { nonce } }) => {
51
+ subscription.nonce = nonce;
52
+ return subscription.save().then(() => {
53
+ this.isTokenizing = false;
54
+ if (!subscription.errorMessage) {
55
+ this.props.onSuccess();
56
+ }
57
+ });
58
+ }).catch(({ message }) => {
59
+ subscription.errors = { base: message };
60
+ this.isTokenizing = false;
61
+ });
62
+ }
63
+
64
+ @computed get isBusy() {
65
+ return !this.getToken || this.isTokenizing;
66
+ }
67
+
68
+ render() {
69
+ const { subscription } = this.props;
70
+ if (!subscription) { return <span />; }
71
+
72
+ return (
73
+ <PaymentFields
74
+ className="payment-form"
75
+ vendor="Braintree"
76
+ onReady={this.onFormReady}
77
+ ref={this.setPaymentRef}
78
+ authorization={subscription.authorization}
79
+ styles={{
80
+ base: {
81
+ color: '#3a3a3a',
82
+ 'line-height': '40px',
83
+ 'font-size': '16px',
84
+ },
85
+ focus: {
86
+ color: 'black',
87
+ },
88
+ }}
89
+ >
90
+ <NetworkActivityOverlay
91
+ message={this.isTokenizing ? 'Subscribing…' : 'Initializing'}
92
+ visible={this.isBusy}
93
+ model={subscription}
94
+ />
95
+ <Box
96
+ margin="medium"
97
+ pad={{ between: 'small' }}
98
+ >
99
+ <h4>
100
+ Card to bill for “{subscription.name}” plan
101
+ (${subscription.formattedPrice}/mo)
102
+ </h4>
103
+
104
+ <CardField
105
+ errorMessage="Invalid Card"
106
+ label="Card Number"
107
+ type="cardNumber"
108
+ placeholder="•••• •••• •••• ••••"
109
+ ref={this.setFieldRef}
110
+ />
111
+ <CardField
112
+ label="Expiration"
113
+ placeholder="MM / YY"
114
+ type="expirationDate"
115
+ errorMessage="Invalid Date"
116
+ ref={this.setFieldRef}
117
+ />
118
+ <CardField
119
+ label="CVV"
120
+ type="cvv"
121
+ errorMessage="Invalid"
122
+ ref={this.setFieldRef}
123
+ />
124
+ <CardField
125
+ label="Postal Code"
126
+ type="postalCode"
127
+ errorMessage="Invalid"
128
+ ref={this.setFieldRef}
129
+ />
130
+
131
+ <WarningNotification
132
+ flex margin="medium"
133
+ message={subscription.errorMessage}
134
+ />
135
+
136
+ </Box>
137
+
138
+ <Box
139
+ justify="end"
140
+ direction="row"
141
+ margin="medium"
142
+ pad={{ between: 'small' }}
143
+ >
144
+ <Button label="Back" onClick={this.props.onCancel} />
145
+ <Button
146
+ onClick={this.isBusy ? null : this.onSubscribe}
147
+ primary icon={<CreditCardIcon />}
148
+ label="Start Subscription" />
149
+ </Box>
150
+ </PaymentFields>
151
+ );
152
+ }
153
+
154
+ }
@@ -0,0 +1,29 @@
1
+ .subscription-choice-layer {
2
+
3
+ .subscriptions-listing {
4
+ min-height: 350px;
5
+ polyline {
6
+ transition: all 0.2s;
7
+ }
8
+ .subscription {
9
+ &:hover {
10
+ polyline {
11
+ stroke-width: 3px;
12
+ stroke: black;
13
+ }
14
+ }
15
+ }
16
+ }
17
+
18
+ .payment-fields-wrapper {
19
+ min-height: 400px;
20
+ position: relative;
21
+ display: flex;
22
+ flex-direction: column;
23
+ }
24
+
25
+ .grommetux-control-icon-next {
26
+ margin-left: 1rem;
27
+ }
28
+
29
+ }