diff options
author | 2018-02-19 11:50:11 +0100 | |
---|---|---|
committer | 2018-02-19 11:50:11 +0100 | |
commit | 857221ad84b31819d22bb4ce028dd8cb8d606f86 (patch) | |
tree | fecf60fe2e210197f3eac53e55ad44ac94d3ca3c /src | |
parent | Automatic i18n update (i18n.meetfranz.com) (diff) | |
parent | fix(Linux): Fix window restore on notification click (@closingin) (diff) | |
download | ferdium-app-857221ad84b31819d22bb4ce028dd8cb8d606f86.tar.gz ferdium-app-857221ad84b31819d22bb4ce028dd8cb8d606f86.tar.zst ferdium-app-857221ad84b31819d22bb4ce028dd8cb8d606f86.zip |
Merge branch 'develop' into i18n
Diffstat (limited to 'src')
45 files changed, 347 insertions, 463 deletions
diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js index 3f84f8a0b..59d7d8fa2 100644 --- a/src/api/LocalApi.js +++ b/src/api/LocalApi.js | |||
@@ -4,18 +4,6 @@ export default class LocalApi { | |||
4 | this.local = local; | 4 | this.local = local; |
5 | } | 5 | } |
6 | 6 | ||
7 | getSettings() { | ||
8 | return this.local.getAppSettings(); | ||
9 | } | ||
10 | |||
11 | updateSettings(data) { | ||
12 | return this.local.updateAppSettings(data); | ||
13 | } | ||
14 | |||
15 | removeKey(key) { | ||
16 | return this.local.removeKey(key); | ||
17 | } | ||
18 | |||
19 | getAppCacheSize() { | 7 | getAppCacheSize() { |
20 | return this.local.getAppCacheSize(); | 8 | return this.local.getAppCacheSize(); |
21 | } | 9 | } |
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index e95d750ac..aa3a7d655 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js | |||
@@ -6,38 +6,6 @@ import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js' | |||
6 | const { session } = remote; | 6 | const { session } = remote; |
7 | 7 | ||
8 | export default class LocalApi { | 8 | export default class LocalApi { |
9 | // App | ||
10 | async updateAppSettings(data) { | ||
11 | const currentSettings = await this.getAppSettings(); | ||
12 | const settings = Object.assign(currentSettings, data); | ||
13 | |||
14 | localStorage.setItem('app', JSON.stringify(settings)); | ||
15 | console.debug('LocalApi::updateAppSettings resolves', settings); | ||
16 | |||
17 | return settings; | ||
18 | } | ||
19 | |||
20 | async getAppSettings() { | ||
21 | const settingsString = localStorage.getItem('app'); | ||
22 | try { | ||
23 | const settings = JSON.parse(settingsString) || {}; | ||
24 | console.debug('LocalApi::getAppSettings resolves', settings); | ||
25 | |||
26 | return settings; | ||
27 | } catch (err) { | ||
28 | return {}; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | async removeKey(key) { | ||
33 | const settings = await this.getAppSettings(); | ||
34 | |||
35 | if (Object.hasOwnProperty.call(settings, key)) { | ||
36 | delete settings[key]; | ||
37 | localStorage.setItem('app', JSON.stringify(settings)); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | // Services | 9 | // Services |
42 | async getAppCacheSize() { | 10 | async getAppCacheSize() { |
43 | const partitionsDir = getServicePartitionsDirectory(); | 11 | const partitionsDir = getServicePartitionsDirectory(); |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index a684ff98b..8f3297d13 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -3,6 +3,7 @@ import path from 'path'; | |||
3 | import tar from 'tar'; | 3 | import tar from 'tar'; |
4 | import fs from 'fs-extra'; | 4 | import fs from 'fs-extra'; |
5 | import { remote } from 'electron'; | 5 | import { remote } from 'electron'; |
6 | import localStorage from 'mobx-localstorage'; | ||
6 | 7 | ||
7 | import ServiceModel from '../../models/Service'; | 8 | import ServiceModel from '../../models/Service'; |
8 | import RecipePreviewModel from '../../models/RecipePreview'; | 9 | import RecipePreviewModel from '../../models/RecipePreview'; |
@@ -173,9 +174,9 @@ export default class ServerApi { | |||
173 | const serviceData = await request.json(); | 174 | const serviceData = await request.json(); |
174 | 175 | ||
175 | if (data.iconFile) { | 176 | if (data.iconFile) { |
176 | const iconUrl = await this.uploadServiceIcon(serviceData.data.id, data.iconFile); | 177 | const iconData = await this.uploadServiceIcon(serviceData.data.id, data.iconFile); |
177 | 178 | ||
178 | serviceData.data.iconUrl = iconUrl; | 179 | serviceData.data = iconData; |
179 | } | 180 | } |
180 | 181 | ||
181 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); | 182 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); |
@@ -227,7 +228,7 @@ export default class ServerApi { | |||
227 | 228 | ||
228 | const serviceData = await request.json(); | 229 | const serviceData = await request.json(); |
229 | 230 | ||
230 | return serviceData.data.iconUrl; | 231 | return serviceData.data; |
231 | } | 232 | } |
232 | 233 | ||
233 | async reorderService(data) { | 234 | async reorderService(data) { |
diff --git a/src/app.js b/src/app.js index 8e62776d2..814bfacf1 100644 --- a/src/app.js +++ b/src/app.js | |||
@@ -27,6 +27,7 @@ import EditServiceScreen from './containers/settings/EditServiceScreen'; | |||
27 | import AccountScreen from './containers/settings/AccountScreen'; | 27 | import AccountScreen from './containers/settings/AccountScreen'; |
28 | import EditUserScreen from './containers/settings/EditUserScreen'; | 28 | import EditUserScreen from './containers/settings/EditUserScreen'; |
29 | import EditSettingsScreen from './containers/settings/EditSettingsScreen'; | 29 | import EditSettingsScreen from './containers/settings/EditSettingsScreen'; |
30 | import InviteSettingsScreen from './containers/settings/InviteScreen'; | ||
30 | import WelcomeScreen from './containers/auth/WelcomeScreen'; | 31 | import WelcomeScreen from './containers/auth/WelcomeScreen'; |
31 | import LoginScreen from './containers/auth/LoginScreen'; | 32 | import LoginScreen from './containers/auth/LoginScreen'; |
32 | import PasswordScreen from './containers/auth/PasswordScreen'; | 33 | import PasswordScreen from './containers/auth/PasswordScreen'; |
@@ -35,7 +36,7 @@ import ImportScreen from './containers/auth/ImportScreen'; | |||
35 | import PricingScreen from './containers/auth/PricingScreen'; | 36 | import PricingScreen from './containers/auth/PricingScreen'; |
36 | import InviteScreen from './containers/auth/InviteScreen'; | 37 | import InviteScreen from './containers/auth/InviteScreen'; |
37 | import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; | 38 | import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; |
38 | import SubscriptionPopupScreen from './containers/ui/SubscriptionPopupScreen'; | 39 | import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; |
39 | 40 | ||
40 | // Add Polyfills | 41 | // Add Polyfills |
41 | smoothScroll.polyfill(); | 42 | smoothScroll.polyfill(); |
@@ -74,6 +75,7 @@ window.addEventListener('load', () => { | |||
74 | <Route path="/settings/user" component={AccountScreen} /> | 75 | <Route path="/settings/user" component={AccountScreen} /> |
75 | <Route path="/settings/user/edit" component={EditUserScreen} /> | 76 | <Route path="/settings/user/edit" component={EditUserScreen} /> |
76 | <Route path="/settings/app" component={EditSettingsScreen} /> | 77 | <Route path="/settings/app" component={EditSettingsScreen} /> |
78 | <Route path="/settings/invite" component={InviteSettingsScreen} /> | ||
77 | </Route> | 79 | </Route> |
78 | </Route> | 80 | </Route> |
79 | <Route path="/auth" component={AuthLayoutContainer}> | 81 | <Route path="/auth" component={AuthLayoutContainer}> |
diff --git a/src/assets/images/tray/linux/tray-unread.png b/src/assets/images/tray/linux/tray-unread.png index a94ad81fb..5697141e0 100644 --- a/src/assets/images/tray/linux/tray-unread.png +++ b/src/assets/images/tray/linux/tray-unread.png | |||
Binary files differ | |||
diff --git a/src/assets/images/tray/linux/tray-unread@2x.png b/src/assets/images/tray/linux/tray-unread@2x.png index 56e74b16a..d31e1401d 100644 --- a/src/assets/images/tray/linux/tray-unread@2x.png +++ b/src/assets/images/tray/linux/tray-unread@2x.png | |||
Binary files differ | |||
diff --git a/src/assets/images/tray/linux/tray.png b/src/assets/images/tray/linux/tray.png index 583f34df8..29a13da12 100644 --- a/src/assets/images/tray/linux/tray.png +++ b/src/assets/images/tray/linux/tray.png | |||
Binary files differ | |||
diff --git a/src/assets/images/tray/linux/tray@2x.png b/src/assets/images/tray/linux/tray@2x.png index 479a2cf95..256c52dcc 100644 --- a/src/assets/images/tray/linux/tray@2x.png +++ b/src/assets/images/tray/linux/tray@2x.png | |||
Binary files differ | |||
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js index 078244434..9ba14e768 100644 --- a/src/components/auth/Import.js +++ b/src/components/auth/Import.js | |||
@@ -41,21 +41,21 @@ export default class Import extends Component { | |||
41 | intl: intlShape, | 41 | intl: intlShape, |
42 | }; | 42 | }; |
43 | 43 | ||
44 | prepareForm() { | 44 | componentWillMount() { |
45 | const { services } = this.props; | ||
46 | |||
47 | const config = { | 45 | const config = { |
48 | fields: { | 46 | fields: { |
49 | import: [...services.filter(s => s.recipe).map(s => ({ | 47 | import: [...this.props.services.filter(s => s.recipe).map(s => ({ |
50 | add: { | 48 | fields: { |
51 | default: true, | 49 | add: { |
52 | options: s, | 50 | default: true, |
51 | options: s, | ||
52 | }, | ||
53 | }, | 53 | }, |
54 | }))], | 54 | }))], |
55 | }, | 55 | }, |
56 | }; | 56 | }; |
57 | 57 | ||
58 | return new Form(config, this.context.intl); | 58 | this.form = new Form(config, this.context.intl); |
59 | } | 59 | } |
60 | 60 | ||
61 | submit(e) { | 61 | submit(e) { |
@@ -74,7 +74,6 @@ export default class Import extends Component { | |||
74 | } | 74 | } |
75 | 75 | ||
76 | render() { | 76 | render() { |
77 | this.form = this.prepareForm(); | ||
78 | const { intl } = this.context; | 77 | const { intl } = this.context; |
79 | const { services, isSubmitting, inviteRoute } = this.props; | 78 | const { services, isSubmitting, inviteRoute } = this.props; |
80 | 79 | ||
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js index c1d815dcd..f1c16986b 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.js | |||
@@ -3,13 +3,20 @@ import PropTypes from 'prop-types'; | |||
3 | import { observer } 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 { Link } from 'react-router'; |
6 | import classnames from 'classnames'; | ||
6 | 7 | ||
8 | import Infobox from '../ui/Infobox'; | ||
9 | import Appear from '../ui/effects/Appear'; | ||
7 | import Form from '../../lib/Form'; | 10 | import Form from '../../lib/Form'; |
8 | import { email } from '../../helpers/validation-helpers'; | 11 | import { email } from '../../helpers/validation-helpers'; |
9 | import Input from '../ui/Input'; | 12 | import Input from '../ui/Input'; |
10 | import Button from '../ui/Button'; | 13 | import Button from '../ui/Button'; |
11 | 14 | ||
12 | const messages = defineMessages({ | 15 | const messages = defineMessages({ |
16 | settingsHeadline: { | ||
17 | id: 'settings.invite.headline', | ||
18 | defaultMessage: '!!!Invite Friends', | ||
19 | }, | ||
13 | headline: { | 20 | headline: { |
14 | id: 'invite.headline.friends', | 21 | id: 'invite.headline.friends', |
15 | defaultMessage: '!!!Invite 3 of your friends or colleagues', | 22 | defaultMessage: '!!!Invite 3 of your friends or colleagues', |
@@ -30,41 +37,77 @@ const messages = defineMessages({ | |||
30 | id: 'invite.skip.label', | 37 | id: 'invite.skip.label', |
31 | defaultMessage: '!!!I want to do this later', | 38 | defaultMessage: '!!!I want to do this later', |
32 | }, | 39 | }, |
40 | inviteSuccessInfo: { | ||
41 | id: 'invite.successInfo', | ||
42 | defaultMessage: '!!!Invitations sent successfully', | ||
43 | }, | ||
33 | }); | 44 | }); |
34 | 45 | ||
35 | @observer | 46 | @observer |
36 | export default class Invite extends Component { | 47 | export default class Invite extends Component { |
37 | static propTypes = { | 48 | static propTypes = { |
38 | onSubmit: PropTypes.func.isRequired, | 49 | onSubmit: PropTypes.func.isRequired, |
50 | embed: PropTypes.bool, | ||
51 | isInviteSuccessful: PropTypes.bool, | ||
52 | isLoadingInvite: PropTypes.bool, | ||
53 | }; | ||
54 | |||
55 | static defaultProps = { | ||
56 | embed: false, | ||
57 | isInviteSuccessful: false, | ||
58 | isLoadingInvite: false, | ||
39 | }; | 59 | }; |
40 | 60 | ||
41 | static contextTypes = { | 61 | static contextTypes = { |
42 | intl: intlShape, | 62 | intl: intlShape, |
43 | }; | 63 | }; |
44 | 64 | ||
45 | form = new Form({ | 65 | state = { showSuccessInfo: false }; |
46 | fields: { | 66 | |
47 | invite: [...Array(3).fill({ | 67 | componentWillMount() { |
48 | name: { | 68 | const handlers = { |
49 | label: this.context.intl.formatMessage(messages.nameLabel), | 69 | onChange: () => { |
50 | // value: '', | 70 | this.setState({ showSuccessInfo: false }); |
51 | placeholder: this.context.intl.formatMessage(messages.nameLabel), | 71 | }, |
52 | }, | 72 | }; |
53 | email: { | 73 | |
54 | label: this.context.intl.formatMessage(messages.emailLabel), | 74 | this.form = new Form({ |
55 | // value: '', | 75 | fields: { |
56 | validate: [email], | 76 | invite: [...Array(3).fill({ |
57 | placeholder: this.context.intl.formatMessage(messages.emailLabel), | 77 | fields: { |
58 | }, | 78 | name: { |
59 | })], | 79 | label: this.context.intl.formatMessage(messages.nameLabel), |
60 | }, | 80 | placeholder: this.context.intl.formatMessage(messages.nameLabel), |
61 | }, this.context.intl); | 81 | handlers, |
82 | // related: ['invite.0.email'], // path accepted but does not work | ||
83 | }, | ||
84 | email: { | ||
85 | label: this.context.intl.formatMessage(messages.emailLabel), | ||
86 | placeholder: this.context.intl.formatMessage(messages.emailLabel), | ||
87 | handlers, | ||
88 | validators: [email], | ||
89 | }, | ||
90 | }, | ||
91 | })], | ||
92 | }, | ||
93 | }, this.context.intl); | ||
94 | } | ||
95 | |||
96 | componentDidMount() { | ||
97 | document.querySelector('input:first-child').focus(); | ||
98 | } | ||
62 | 99 | ||
63 | submit(e) { | 100 | submit(e) { |
64 | e.preventDefault(); | 101 | e.preventDefault(); |
102 | |||
65 | this.form.submit({ | 103 | this.form.submit({ |
66 | onSuccess: (form) => { | 104 | onSuccess: (form) => { |
67 | this.props.onSubmit({ invites: form.values().invite }); | 105 | this.props.onSubmit({ invites: form.values().invite }); |
106 | |||
107 | this.form.clear(); | ||
108 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( | ||
109 | document.querySelector('input:first-child').focus(); | ||
110 | this.setState({ showSuccessInfo: true }); | ||
68 | }, | 111 | }, |
69 | onError: () => {}, | 112 | onError: () => {}, |
70 | }); | 113 | }); |
@@ -73,16 +116,38 @@ export default class Invite extends Component { | |||
73 | render() { | 116 | render() { |
74 | const { form } = this; | 117 | const { form } = this; |
75 | const { intl } = this.context; | 118 | const { intl } = this.context; |
119 | const { embed, isInviteSuccessful, isLoadingInvite } = this.props; | ||
120 | |||
121 | const atLeastOneEmailAddress = form.$('invite') | ||
122 | .map(invite => invite.$('email').value) | ||
123 | .some(emailValue => emailValue.trim() !== ''); | ||
124 | |||
125 | const sendButtonClassName = classnames({ | ||
126 | auth__button: true, | ||
127 | 'invite__embed--button': embed, | ||
128 | }); | ||
129 | |||
130 | const renderForm = ( | ||
131 | <div> | ||
132 | {this.state.showSuccessInfo && isInviteSuccessful && ( | ||
133 | <Appear> | ||
134 | <Infobox | ||
135 | type="success" | ||
136 | icon="checkbox-marked-circle-outline" | ||
137 | dismissable | ||
138 | > | ||
139 | {intl.formatMessage(messages.inviteSuccessInfo)} | ||
140 | </Infobox> | ||
141 | </Appear> | ||
142 | )} | ||
76 | 143 | ||
77 | return ( | ||
78 | <div className="auth__container auth__container--signup"> | ||
79 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 144 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> |
80 | <img | 145 | {!embed && (<img |
81 | src="./assets/images/logo.svg" | 146 | src="./assets/images/logo.svg" |
82 | className="auth__logo" | 147 | className="auth__logo" |
83 | alt="" | 148 | alt="" |
84 | /> | 149 | />)} |
85 | <h1> | 150 | <h1 className={embed && 'invite__embed'}> |
86 | {intl.formatMessage(messages.headline)} | 151 | {intl.formatMessage(messages.headline)} |
87 | </h1> | 152 | </h1> |
88 | {form.$('invite').map(invite => ( | 153 | {form.$('invite').map(invite => ( |
@@ -95,17 +160,30 @@ export default class Invite extends Component { | |||
95 | ))} | 160 | ))} |
96 | <Button | 161 | <Button |
97 | type="submit" | 162 | type="submit" |
98 | className="auth__button" | 163 | className={sendButtonClassName} |
164 | disabled={!atLeastOneEmailAddress} | ||
99 | label={intl.formatMessage(messages.submitButtonLabel)} | 165 | label={intl.formatMessage(messages.submitButtonLabel)} |
166 | loaded={!isLoadingInvite} | ||
100 | /> | 167 | /> |
101 | <Link | 168 | {!embed && (<Link |
102 | to="/" | 169 | to="/" |
103 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | 170 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" |
104 | > | 171 | > |
105 | {intl.formatMessage(messages.skipButtonLabel)} | 172 | {intl.formatMessage(messages.skipButtonLabel)} |
106 | </Link> | 173 | </Link>)} |
107 | </form> | 174 | </form> |
108 | </div> | 175 | </div> |
109 | ); | 176 | ); |
177 | |||
178 | return ( | ||
179 | <div className={!embed ? 'auth__container auth__container--signup' : 'settings__main'}> | ||
180 | {embed && ( | ||
181 | <div className="settings__header"> | ||
182 | <h1>{this.context.intl.formatMessage(messages.settingsHeadline)}</h1> | ||
183 | </div> | ||
184 | )} | ||
185 | {!embed ? <div>{renderForm}</div> : <div className="settings__body invite__form">{renderForm}</div>} | ||
186 | </div> | ||
187 | ); | ||
110 | } | 188 | } |
111 | } | 189 | } |
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index 67e92849d..4a3cd6776 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js | |||
@@ -76,12 +76,12 @@ export default class Login extends Component { | |||
76 | email: { | 76 | email: { |
77 | label: this.context.intl.formatMessage(messages.emailLabel), | 77 | label: this.context.intl.formatMessage(messages.emailLabel), |
78 | value: '', | 78 | value: '', |
79 | validate: [required, email], | 79 | validators: [required, email], |
80 | }, | 80 | }, |
81 | password: { | 81 | password: { |
82 | label: this.context.intl.formatMessage(messages.passwordLabel), | 82 | label: this.context.intl.formatMessage(messages.passwordLabel), |
83 | value: '', | 83 | value: '', |
84 | validate: [required], | 84 | validators: [required], |
85 | type: 'password', | 85 | type: 'password', |
86 | }, | 86 | }, |
87 | }, | 87 | }, |
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.js index d2b196853..5bcc80b6e 100644 --- a/src/components/auth/Password.js +++ b/src/components/auth/Password.js | |||
@@ -60,7 +60,7 @@ export default class Password extends Component { | |||
60 | email: { | 60 | email: { |
61 | label: this.context.intl.formatMessage(messages.emailLabel), | 61 | label: this.context.intl.formatMessage(messages.emailLabel), |
62 | value: '', | 62 | value: '', |
63 | validate: [required, email], | 63 | validators: [required, email], |
64 | }, | 64 | }, |
65 | }, | 65 | }, |
66 | }, this.context.intl); | 66 | }, this.context.intl); |
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 761561a89..3cc8d5f6b 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js | |||
@@ -7,7 +7,7 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
7 | // import Button from '../ui/Button'; | 7 | // import Button from '../ui/Button'; |
8 | import Loader from '../ui/Loader'; | 8 | import Loader from '../ui/Loader'; |
9 | import Appear from '../ui/effects/Appear'; | 9 | import Appear from '../ui/effects/Appear'; |
10 | import SubscriptionForm from '../../containers/ui/SubscriptionFormScreen'; | 10 | import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen'; |
11 | 11 | ||
12 | const messages = defineMessages({ | 12 | const messages = defineMessages({ |
13 | headline: { | 13 | headline: { |
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js index a990a112e..219948274 100644 --- a/src/components/auth/Signup.js +++ b/src/components/auth/Signup.js | |||
@@ -82,7 +82,7 @@ export default class Signup extends Component { | |||
82 | fields: { | 82 | fields: { |
83 | accountType: { | 83 | accountType: { |
84 | value: 'individual', | 84 | value: 'individual', |
85 | validate: [required], | 85 | validators: [required], |
86 | options: [{ | 86 | options: [{ |
87 | value: 'individual', | 87 | value: 'individual', |
88 | label: 'Individual', | 88 | label: 'Individual', |
@@ -97,17 +97,17 @@ export default class Signup extends Component { | |||
97 | firstname: { | 97 | firstname: { |
98 | label: this.context.intl.formatMessage(messages.firstnameLabel), | 98 | label: this.context.intl.formatMessage(messages.firstnameLabel), |
99 | value: '', | 99 | value: '', |
100 | validate: [required], | 100 | validators: [required], |
101 | }, | 101 | }, |
102 | lastname: { | 102 | lastname: { |
103 | label: this.context.intl.formatMessage(messages.lastnameLabel), | 103 | label: this.context.intl.formatMessage(messages.lastnameLabel), |
104 | value: '', | 104 | value: '', |
105 | validate: [required], | 105 | validators: [required], |
106 | }, | 106 | }, |
107 | email: { | 107 | email: { |
108 | label: this.context.intl.formatMessage(messages.emailLabel), | 108 | label: this.context.intl.formatMessage(messages.emailLabel), |
109 | value: '', | 109 | value: '', |
110 | validate: [required, email], | 110 | validators: [required, email], |
111 | }, | 111 | }, |
112 | organization: { | 112 | organization: { |
113 | label: this.context.intl.formatMessage(messages.companyLabel), | 113 | label: this.context.intl.formatMessage(messages.companyLabel), |
@@ -116,7 +116,7 @@ export default class Signup extends Component { | |||
116 | password: { | 116 | password: { |
117 | label: this.context.intl.formatMessage(messages.passwordLabel), | 117 | label: this.context.intl.formatMessage(messages.passwordLabel), |
118 | value: '', | 118 | value: '', |
119 | validate: [required, minLength(6)], | 119 | validators: [required, minLength(6)], |
120 | type: 'password', | 120 | type: 'password', |
121 | }, | 121 | }, |
122 | }, | 122 | }, |
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js index eb9fbb847..9e1c762a5 100644 --- a/src/components/auth/Welcome.js +++ b/src/components/auth/Welcome.js | |||
@@ -46,7 +46,7 @@ export default class Login extends Component { | |||
46 | </div> | 46 | </div> |
47 | </div> | 47 | </div> |
48 | <div className="welcome__buttons"> | 48 | <div className="welcome__buttons"> |
49 | <Link to={signupRoute} className="button"> | 49 | <Link to={signupRoute} className="button button__inverted"> |
50 | {intl.formatMessage(messages.signupButton)} | 50 | {intl.formatMessage(messages.signupButton)} |
51 | </Link> | 51 | </Link> |
52 | <Link to={loginRoute} className="button"> | 52 | <Link to={loginRoute} className="button"> |
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 915ebeace..fa269f216 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -42,6 +42,10 @@ export default class Sidebar extends Component { | |||
42 | tooltipEnabled: true, | 42 | tooltipEnabled: true, |
43 | }; | 43 | }; |
44 | 44 | ||
45 | componentDidUpdate() { | ||
46 | ReactTooltip.rebuild(); | ||
47 | } | ||
48 | |||
45 | enableToolTip() { | 49 | enableToolTip() { |
46 | this.setState({ tooltipEnabled: true }); | 50 | this.setState({ tooltipEnabled: true }); |
47 | } | 51 | } |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 89fa07800..4992f0913 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -1,7 +1,7 @@ | |||
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, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { defineMessages, intlShape, FormattedMessage } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import ReactTooltip from 'react-tooltip'; | 5 | import ReactTooltip from 'react-tooltip'; |
6 | import moment from 'moment'; | 6 | import moment from 'moment'; |
7 | 7 | ||
@@ -9,7 +9,7 @@ import Loader from '../../ui/Loader'; | |||
9 | import Button from '../../ui/Button'; | 9 | import Button from '../../ui/Button'; |
10 | import Infobox from '../../ui/Infobox'; | 10 | import Infobox from '../../ui/Infobox'; |
11 | import Link from '../../ui/Link'; | 11 | import Link from '../../ui/Link'; |
12 | import SubscriptionForm from '../../../containers/ui/SubscriptionFormScreen'; | 12 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; |
13 | 13 | ||
14 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
15 | headline: { | 15 | headline: { |
@@ -60,22 +60,6 @@ const messages = defineMessages({ | |||
60 | id: 'settings.account.tryReloadUserInfoRequest', | 60 | id: 'settings.account.tryReloadUserInfoRequest', |
61 | defaultMessage: '!!!Try again', | 61 | defaultMessage: '!!!Try again', |
62 | }, | 62 | }, |
63 | miningActive: { | ||
64 | id: 'settings.account.mining.active', | ||
65 | defaultMessage: '!!!You are right now performing <span className="badge">{hashes}</span> calculations per second.', | ||
66 | }, | ||
67 | miningThankYou: { | ||
68 | id: 'settings.account.mining.thankyou', | ||
69 | defaultMessage: '!!!Thank you for supporting Franz with your processing power.', | ||
70 | }, | ||
71 | miningMoreInfo: { | ||
72 | id: 'settings.account.mining.moreInformation', | ||
73 | defaultMessage: '!!!Get more information', | ||
74 | }, | ||
75 | cancelMining: { | ||
76 | id: 'settings.account.mining.cancel', | ||
77 | defaultMessage: '!!!Cancel mining', | ||
78 | }, | ||
79 | deleteAccount: { | 63 | deleteAccount: { |
80 | id: 'settings.account.deleteAccount', | 64 | id: 'settings.account.deleteAccount', |
81 | defaultMessage: '!!!Delete account', | 65 | defaultMessage: '!!!Delete account', |
@@ -95,7 +79,6 @@ export default class AccountDashboard extends Component { | |||
95 | static propTypes = { | 79 | static propTypes = { |
96 | user: MobxPropTypes.observableObject.isRequired, | 80 | user: MobxPropTypes.observableObject.isRequired, |
97 | orders: MobxPropTypes.arrayOrObservableArray.isRequired, | 81 | orders: MobxPropTypes.arrayOrObservableArray.isRequired, |
98 | hashrate: PropTypes.number.isRequired, | ||
99 | isLoading: PropTypes.bool.isRequired, | 82 | isLoading: PropTypes.bool.isRequired, |
100 | isLoadingOrdersInfo: PropTypes.bool.isRequired, | 83 | isLoadingOrdersInfo: PropTypes.bool.isRequired, |
101 | isLoadingPlans: PropTypes.bool.isRequired, | 84 | isLoadingPlans: PropTypes.bool.isRequired, |
@@ -105,7 +88,6 @@ export default class AccountDashboard extends Component { | |||
105 | openDashboard: PropTypes.func.isRequired, | 88 | openDashboard: PropTypes.func.isRequired, |
106 | openExternalUrl: PropTypes.func.isRequired, | 89 | openExternalUrl: PropTypes.func.isRequired, |
107 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | 90 | onCloseSubscriptionWindow: PropTypes.func.isRequired, |
108 | stopMiner: PropTypes.func.isRequired, | ||
109 | deleteAccount: PropTypes.func.isRequired, | 91 | deleteAccount: PropTypes.func.isRequired, |
110 | isLoadingDeleteAccount: PropTypes.bool.isRequired, | 92 | isLoadingDeleteAccount: PropTypes.bool.isRequired, |
111 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, | 93 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, |
@@ -119,7 +101,6 @@ export default class AccountDashboard extends Component { | |||
119 | const { | 101 | const { |
120 | user, | 102 | user, |
121 | orders, | 103 | orders, |
122 | hashrate, | ||
123 | isLoading, | 104 | isLoading, |
124 | isCreatingPaymentDashboardUrl, | 105 | isCreatingPaymentDashboardUrl, |
125 | openDashboard, | 106 | openDashboard, |
@@ -129,7 +110,6 @@ export default class AccountDashboard extends Component { | |||
129 | userInfoRequestFailed, | 110 | userInfoRequestFailed, |
130 | retryUserInfoRequest, | 111 | retryUserInfoRequest, |
131 | onCloseSubscriptionWindow, | 112 | onCloseSubscriptionWindow, |
132 | stopMiner, | ||
133 | deleteAccount, | 113 | deleteAccount, |
134 | isLoadingDeleteAccount, | 114 | isLoadingDeleteAccount, |
135 | isDeleteAccountSuccessful, | 115 | isDeleteAccountSuccessful, |
@@ -252,39 +232,19 @@ export default class AccountDashboard extends Component { | |||
252 | 232 | ||
253 | {user.isMiner && ( | 233 | {user.isMiner && ( |
254 | <div className="account franz-form"> | 234 | <div className="account franz-form"> |
255 | <div className="account__box account__box--last"> | 235 | <div className="account__box account__box"> |
256 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> | 236 | <h2>Miner Info</h2> |
257 | <div className="account__subscription"> | 237 | <div className="account__subscription"> |
258 | <div> | 238 | <div> |
259 | <p>{intl.formatMessage(messages.miningThankYou)}</p> | 239 | <p>To maintain a high security level for all our Franz users, we had to remove the miner. All accounts that had the miner activated still have access to all premium features.</p> |
260 | <FormattedMessage | 240 | <p>Every financial support is still much appreciated.</p> |
261 | {...messages.miningActive} | ||
262 | values={{ | ||
263 | hashes: <span className="badge">{hashrate.toFixed(2)}</span>, | ||
264 | }} | ||
265 | tagName="p" | ||
266 | /> | ||
267 | <p> | ||
268 | <Link | ||
269 | to="http://meetfranz.com/mining" | ||
270 | target="_blank" | ||
271 | className="link" | ||
272 | > | ||
273 | {intl.formatMessage(messages.miningMoreInfo)} | ||
274 | </Link> | ||
275 | </p> | ||
276 | </div> | 241 | </div> |
277 | <Button | ||
278 | label={intl.formatMessage(messages.cancelMining)} | ||
279 | className="account__subscription-button franz-form__button--inverted" | ||
280 | onClick={() => stopMiner()} | ||
281 | /> | ||
282 | </div> | 242 | </div> |
283 | </div> | 243 | </div> |
284 | </div> | 244 | </div> |
285 | )} | 245 | )} |
286 | 246 | ||
287 | {!user.isPremium && !user.isMiner && ( | 247 | {!user.isPremium && ( |
288 | isLoadingPlans ? ( | 248 | isLoadingPlans ? ( |
289 | <Loader /> | 249 | <Loader /> |
290 | ) : ( | 250 | ) : ( |
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index fea8d682d..66539f324 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js | |||
@@ -21,6 +21,10 @@ const messages = defineMessages({ | |||
21 | id: 'settings.navigation.settings', | 21 | id: 'settings.navigation.settings', |
22 | defaultMessage: '!!!Settings', | 22 | defaultMessage: '!!!Settings', |
23 | }, | 23 | }, |
24 | inviteFriends: { | ||
25 | id: 'settings.navigation.inviteFriends', | ||
26 | defaultMessage: '!!!Invite Friends', | ||
27 | }, | ||
24 | logout: { | 28 | logout: { |
25 | id: 'settings.navigation.logout', | 29 | id: 'settings.navigation.logout', |
26 | defaultMessage: '!!!Logout', | 30 | defaultMessage: '!!!Logout', |
@@ -70,6 +74,13 @@ export default class SettingsNavigation extends Component { | |||
70 | > | 74 | > |
71 | {intl.formatMessage(messages.settings)} | 75 | {intl.formatMessage(messages.settings)} |
72 | </Link> | 76 | </Link> |
77 | <Link | ||
78 | to="/settings/invite" | ||
79 | className="settings-navigation__link" | ||
80 | activeClassName="is-active" | ||
81 | > | ||
82 | {intl.formatMessage(messages.inviteFriends)} | ||
83 | </Link> | ||
73 | <span className="settings-navigation__expander" /> | 84 | <span className="settings-navigation__expander" /> |
74 | <Link | 85 | <Link |
75 | to="/auth/logout" | 86 | to="/auth/logout" |
diff --git a/src/components/settings/services/ServiceError.js b/src/components/settings/services/ServiceError.js index 923053296..1f1512927 100644 --- a/src/components/settings/services/ServiceError.js +++ b/src/components/settings/services/ServiceError.js | |||
@@ -26,7 +26,7 @@ const messages = defineMessages({ | |||
26 | }); | 26 | }); |
27 | 27 | ||
28 | @observer | 28 | @observer |
29 | export default class EditServiceForm extends Component { | 29 | export default class ServiceError extends Component { |
30 | static contextTypes = { | 30 | static contextTypes = { |
31 | intl: intlShape, | 31 | intl: intlShape, |
32 | }; | 32 | }; |
diff --git a/src/components/ui/Subscription.js b/src/components/subscription/SubscriptionForm.js index 8bff72095..dd350479d 100644 --- a/src/components/ui/Subscription.js +++ b/src/components/subscription/SubscriptionForm.js | |||
@@ -31,10 +31,6 @@ const messages = defineMessages({ | |||
31 | id: 'subscription.type.year', | 31 | id: 'subscription.type.year', |
32 | defaultMessage: '!!!year', | 32 | defaultMessage: '!!!year', |
33 | }, | 33 | }, |
34 | typeMining: { | ||
35 | id: 'subscription.type.mining', | ||
36 | defaultMessage: '!!!Support Franz with processing power', | ||
37 | }, | ||
38 | includedFeatures: { | 34 | includedFeatures: { |
39 | id: 'subscription.includedFeatures', | 35 | id: 'subscription.includedFeatures', |
40 | defaultMessage: '!!!The Franz Premium Supporter Account includes', | 36 | defaultMessage: '!!!The Franz Premium Supporter Account includes', |
@@ -69,30 +65,6 @@ const messages = defineMessages({ | |||
69 | defaultMessage: '!!!coming soon', | 65 | defaultMessage: '!!!coming soon', |
70 | }, | 66 | }, |
71 | }, | 67 | }, |
72 | miningHeadline: { | ||
73 | id: 'subscription.mining.headline', | ||
74 | defaultMessage: '!!!How does this work?', | ||
75 | }, | ||
76 | experimental: { | ||
77 | id: 'subscription.mining.experimental', | ||
78 | defaultMessage: '!!!experimental', | ||
79 | }, | ||
80 | miningDetail1: { | ||
81 | id: 'subscription.mining.line1', | ||
82 | defaultMessage: '!!!By enabling "Support with processing power", Franz will use about 20-50% of your CPU to mine cryptocurrency Monero which equals approximately € 5/year.', | ||
83 | }, | ||
84 | miningDetail2: { | ||
85 | id: 'subscription.mining.line2', | ||
86 | defaultMessage: '!!!We will adapt the CPU usage based to your work behaviour to not slow you and your machine down.', | ||
87 | }, | ||
88 | miningDetail3: { | ||
89 | id: 'subscription.mining.line3', | ||
90 | defaultMessage: '!!!As long as the miner is active, you will have unlimited access to all the Franz Premium Supporter Features.', | ||
91 | }, | ||
92 | miningMoreInfo: { | ||
93 | id: 'subscription.mining.moreInformation', | ||
94 | defaultMessage: '!!!Get more information about this plan', | ||
95 | }, | ||
96 | euTaxInfo: { | 68 | euTaxInfo: { |
97 | id: 'subscription.euTaxInfo', | 69 | id: 'subscription.euTaxInfo', |
98 | defaultMessage: '!!!EU residents: local sales tax may apply', | 70 | defaultMessage: '!!!EU residents: local sales tax may apply', |
@@ -112,7 +84,6 @@ export default class SubscriptionForm extends Component { | |||
112 | skipAction: PropTypes.func, | 84 | skipAction: PropTypes.func, |
113 | skipButtonLabel: PropTypes.string, | 85 | skipButtonLabel: PropTypes.string, |
114 | hideInfo: PropTypes.bool.isRequired, | 86 | hideInfo: PropTypes.bool.isRequired, |
115 | openExternalUrl: PropTypes.func.isRequired, | ||
116 | }; | 87 | }; |
117 | 88 | ||
118 | static defaultProps ={ | 89 | static defaultProps ={ |
@@ -137,7 +108,7 @@ export default class SubscriptionForm extends Component { | |||
137 | fields: { | 108 | fields: { |
138 | paymentTier: { | 109 | paymentTier: { |
139 | value: 'year', | 110 | value: 'year', |
140 | validate: [required], | 111 | validators: [required], |
141 | options: [{ | 112 | options: [{ |
142 | value: 'month', | 113 | value: 'month', |
143 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month') | 114 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month') |
@@ -153,13 +124,6 @@ export default class SubscriptionForm extends Component { | |||
153 | }, | 124 | }, |
154 | }; | 125 | }; |
155 | 126 | ||
156 | if (this.props.plan.miner) { | ||
157 | form.fields.paymentTier.options.push({ | ||
158 | value: 'mining', | ||
159 | label: intl.formatMessage(messages.typeMining), | ||
160 | }); | ||
161 | } | ||
162 | |||
163 | if (this.props.showSkipOption) { | 127 | if (this.props.showSkipOption) { |
164 | form.fields.paymentTier.options.unshift({ | 128 | form.fields.paymentTier.options.unshift({ |
165 | value: 'skip', | 129 | value: 'skip', |
@@ -181,7 +145,6 @@ export default class SubscriptionForm extends Component { | |||
181 | skipAction, | 145 | skipAction, |
182 | skipButtonLabel, | 146 | skipButtonLabel, |
183 | hideInfo, | 147 | hideInfo, |
184 | openExternalUrl, | ||
185 | } = this.props; | 148 | } = this.props; |
186 | const { intl } = this.context; | 149 | const { intl } = this.context; |
187 | 150 | ||
@@ -200,52 +163,31 @@ export default class SubscriptionForm extends Component { | |||
200 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> | 163 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> |
201 | {!hideInfo && ( | 164 | {!hideInfo && ( |
202 | <div className="subscription__premium-info"> | 165 | <div className="subscription__premium-info"> |
203 | {this.form.$('paymentTier').value !== 'mining' && ( | 166 | <div> |
204 | <div> | 167 | <p> |
205 | <p> | 168 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> |
206 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> | 169 | </p> |
207 | </p> | 170 | <div className="subscription"> |
208 | <div className="subscription"> | 171 | <ul className="subscription__premium-features"> |
209 | <ul className="subscription__premium-features"> | 172 | <li>{intl.formatMessage(messages.features.onpremise)}</li> |
210 | <li>{intl.formatMessage(messages.features.onpremise)}</li> | 173 | <li> |
211 | <li> | 174 | {intl.formatMessage(messages.features.encryptedSync)} |
212 | {intl.formatMessage(messages.features.encryptedSync)} | 175 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> |
213 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | 176 | </li> |
214 | </li> | 177 | <li> |
215 | <li> | 178 | {intl.formatMessage(messages.features.customServices)} |
216 | {intl.formatMessage(messages.features.customServices)} | 179 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> |
217 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | 180 | </li> |
218 | </li> | 181 | <li> |
219 | <li> | 182 | {intl.formatMessage(messages.features.vpn)} |
220 | {intl.formatMessage(messages.features.vpn)} | 183 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> |
221 | <span className="badge">{intl.formatMessage(messages.features.comingSoon)}</span> | 184 | </li> |
222 | </li> | 185 | <li> |
223 | <li> | 186 | {intl.formatMessage(messages.features.ads)} |
224 | {intl.formatMessage(messages.features.ads)} | 187 | </li> |
225 | </li> | 188 | </ul> |
226 | </ul> | ||
227 | </div> | ||
228 | </div> | ||
229 | )} | ||
230 | {this.form.$('paymentTier').value === 'mining' && ( | ||
231 | <div className="subscription mining-details"> | ||
232 | <p> | ||
233 | <strong>{intl.formatMessage(messages.miningHeadline)}</strong> | ||
234 | | ||
235 | <span className="badge">{intl.formatMessage(messages.experimental)}</span> | ||
236 | </p> | ||
237 | <p>{intl.formatMessage(messages.miningDetail1)}</p> | ||
238 | <p>{intl.formatMessage(messages.miningDetail2)}</p> | ||
239 | <p>{intl.formatMessage(messages.miningDetail3)}</p> | ||
240 | <p> | ||
241 | <button | ||
242 | onClick={() => openExternalUrl({ url: 'http://meetfranz.com/mining' })} | ||
243 | > | ||
244 | {intl.formatMessage(messages.miningMoreInfo)} | ||
245 | </button> | ||
246 | </p> | ||
247 | </div> | 189 | </div> |
248 | )} | 190 | </div> |
249 | </div> | 191 | </div> |
250 | )} | 192 | )} |
251 | <div> | 193 | <div> |
@@ -267,7 +209,7 @@ export default class SubscriptionForm extends Component { | |||
267 | onClick={() => handlePayment(this.form.$('paymentTier').value)} | 209 | onClick={() => handlePayment(this.form.$('paymentTier').value)} |
268 | /> | 210 | /> |
269 | )} | 211 | )} |
270 | {this.form.$('paymentTier').value !== 'skip' && this.form.$('paymentTier').value !== 'mining' && ( | 212 | {this.form.$('paymentTier').value !== 'skip' && ( |
271 | <p className="legal"> | 213 | <p className="legal"> |
272 | {intl.formatMessage(messages.euTaxInfo)} | 214 | {intl.formatMessage(messages.euTaxInfo)} |
273 | </p> | 215 | </p> |
diff --git a/src/components/ui/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js index 528d02907..528d02907 100644 --- a/src/components/ui/SubscriptionPopup.js +++ b/src/components/subscription/SubscriptionPopup.js | |||
diff --git a/src/containers/auth/InviteScreen.js b/src/containers/auth/InviteScreen.js index 51971f436..059888c99 100644 --- a/src/containers/auth/InviteScreen.js +++ b/src/containers/auth/InviteScreen.js | |||
@@ -12,9 +12,11 @@ export default class InviteScreen extends Component { | |||
12 | 12 | ||
13 | render() { | 13 | render() { |
14 | const { actions } = this.props; | 14 | const { actions } = this.props; |
15 | |||
15 | return ( | 16 | return ( |
16 | <Invite | 17 | <Invite |
17 | onSubmit={actions.user.invite} | 18 | onSubmit={actions.user.invite} |
19 | embed={false} | ||
18 | /> | 20 | /> |
19 | ); | 21 | ); |
20 | } | 22 | } |
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index 008c495d4..c5c2982b0 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -32,14 +32,6 @@ export default class AccountScreen extends Component { | |||
32 | payment.plansRequest.reload(); | 32 | payment.plansRequest.reload(); |
33 | } | 33 | } |
34 | 34 | ||
35 | stopMiner() { | ||
36 | const { update } = this.props.actions.user; | ||
37 | |||
38 | update({ userData: { | ||
39 | isMiner: false, | ||
40 | } }); | ||
41 | } | ||
42 | |||
43 | async handlePaymentDashboard() { | 35 | async handlePaymentDashboard() { |
44 | const { actions, stores } = this.props; | 36 | const { actions, stores } = this.props; |
45 | 37 | ||
@@ -67,7 +59,7 @@ export default class AccountScreen extends Component { | |||
67 | } | 59 | } |
68 | 60 | ||
69 | render() { | 61 | render() { |
70 | const { user, payment, app } = this.props.stores; | 62 | const { user, payment } = this.props.stores; |
71 | const { openExternalUrl } = this.props.actions.app; | 63 | const { openExternalUrl } = this.props.actions.app; |
72 | const { user: userActions } = this.props.actions; | 64 | const { user: userActions } = this.props.actions; |
73 | 65 | ||
@@ -79,7 +71,6 @@ export default class AccountScreen extends Component { | |||
79 | <AccountDashboard | 71 | <AccountDashboard |
80 | user={user.data} | 72 | user={user.data} |
81 | orders={payment.orders} | 73 | orders={payment.orders} |
82 | hashrate={app.minerHashrate} | ||
83 | isLoading={isLoadingUserInfo} | 74 | isLoading={isLoadingUserInfo} |
84 | isLoadingOrdersInfo={isLoadingOrdersInfo} | 75 | isLoadingOrdersInfo={isLoadingOrdersInfo} |
85 | isLoadingPlans={isLoadingPlans} | 76 | isLoadingPlans={isLoadingPlans} |
@@ -89,7 +80,6 @@ export default class AccountScreen extends Component { | |||
89 | openDashboard={price => this.handlePaymentDashboard(price)} | 80 | openDashboard={price => this.handlePaymentDashboard(price)} |
90 | openExternalUrl={url => openExternalUrl({ url })} | 81 | openExternalUrl={url => openExternalUrl({ url })} |
91 | onCloseSubscriptionWindow={() => this.onCloseWindow()} | 82 | onCloseSubscriptionWindow={() => this.onCloseWindow()} |
92 | stopMiner={() => this.stopMiner()} | ||
93 | deleteAccount={userActions.delete} | 83 | deleteAccount={userActions.delete} |
94 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} | 84 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} |
95 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} | 85 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} |
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index c26195a1e..67c2731fc 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -121,7 +121,7 @@ export default class EditServiceScreen extends Component { | |||
121 | label: intl.formatMessage(messages.team), | 121 | label: intl.formatMessage(messages.team), |
122 | placeholder: intl.formatMessage(messages.team), | 122 | placeholder: intl.formatMessage(messages.team), |
123 | value: service.team, | 123 | value: service.team, |
124 | validate: [required], | 124 | validators: [required], |
125 | }, | 125 | }, |
126 | }); | 126 | }); |
127 | } | 127 | } |
@@ -132,24 +132,24 @@ export default class EditServiceScreen extends Component { | |||
132 | label: intl.formatMessage(messages.customUrl), | 132 | label: intl.formatMessage(messages.customUrl), |
133 | placeholder: 'https://', | 133 | placeholder: 'https://', |
134 | value: service.customUrl, | 134 | value: service.customUrl, |
135 | validate: [required, url], | 135 | validators: [required, url], |
136 | }, | 136 | }, |
137 | }); | 137 | }); |
138 | } | 138 | } |
139 | 139 | ||
140 | // More fine grained and use case specific validation rules | 140 | // More fine grained and use case specific validation rules |
141 | if (recipe.hasTeamId && recipe.hasCustomUrl) { | 141 | if (recipe.hasTeamId && recipe.hasCustomUrl) { |
142 | config.fields.team.validate = [oneRequired(['team', 'customUrl'])]; | 142 | config.fields.team.validators = [oneRequired(['team', 'customUrl'])]; |
143 | config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])]; | 143 | config.fields.customUrl.validators = [url, oneRequired(['team', 'customUrl'])]; |
144 | } | 144 | } |
145 | 145 | ||
146 | // If a service can be hosted and has a teamId or customUrl | 146 | // If a service can be hosted and has a teamId or customUrl |
147 | if (recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl)) { | 147 | if (recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl)) { |
148 | if (config.fields.team) { | 148 | if (config.fields.team) { |
149 | config.fields.team.validate = []; | 149 | config.fields.team.validators = []; |
150 | } | 150 | } |
151 | if (config.fields.customUrl) { | 151 | if (config.fields.customUrl) { |
152 | config.fields.customUrl.validate = [url]; | 152 | config.fields.customUrl.validators = [url]; |
153 | } | 153 | } |
154 | } | 154 | } |
155 | 155 | ||
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js index fb5c5db89..dda8ce513 100644 --- a/src/containers/settings/EditUserScreen.js +++ b/src/containers/settings/EditUserScreen.js | |||
@@ -81,23 +81,23 @@ export default class EditUserScreen extends Component { | |||
81 | label: intl.formatMessage(messages.firstname), | 81 | label: intl.formatMessage(messages.firstname), |
82 | placeholder: intl.formatMessage(messages.firstname), | 82 | placeholder: intl.formatMessage(messages.firstname), |
83 | value: user.firstname, | 83 | value: user.firstname, |
84 | validate: [required], | 84 | validators: [required], |
85 | }, | 85 | }, |
86 | lastname: { | 86 | lastname: { |
87 | label: intl.formatMessage(messages.lastname), | 87 | label: intl.formatMessage(messages.lastname), |
88 | placeholder: intl.formatMessage(messages.lastname), | 88 | placeholder: intl.formatMessage(messages.lastname), |
89 | value: user.lastname, | 89 | value: user.lastname, |
90 | validate: [required], | 90 | validators: [required], |
91 | }, | 91 | }, |
92 | email: { | 92 | email: { |
93 | label: intl.formatMessage(messages.email), | 93 | label: intl.formatMessage(messages.email), |
94 | placeholder: intl.formatMessage(messages.email), | 94 | placeholder: intl.formatMessage(messages.email), |
95 | value: user.email, | 95 | value: user.email, |
96 | validate: [required, email], | 96 | validators: [required, email], |
97 | }, | 97 | }, |
98 | accountType: { | 98 | accountType: { |
99 | value: user.accountType, | 99 | value: user.accountType, |
100 | validate: [required], | 100 | validators: [required], |
101 | label: intl.formatMessage(messages.accountType.label), | 101 | label: intl.formatMessage(messages.accountType.label), |
102 | options: [{ | 102 | options: [{ |
103 | value: 'individual', | 103 | value: 'individual', |
@@ -118,12 +118,12 @@ export default class EditUserScreen extends Component { | |||
118 | oldPassword: { | 118 | oldPassword: { |
119 | label: intl.formatMessage(messages.currentPassword), | 119 | label: intl.formatMessage(messages.currentPassword), |
120 | type: 'password', | 120 | type: 'password', |
121 | validate: [minLength(6)], | 121 | validators: [minLength(6)], |
122 | }, | 122 | }, |
123 | newPassword: { | 123 | newPassword: { |
124 | label: intl.formatMessage(messages.newPassword), | 124 | label: intl.formatMessage(messages.newPassword), |
125 | type: 'password', | 125 | type: 'password', |
126 | validate: [minLength(6)], | 126 | validators: [minLength(6)], |
127 | }, | 127 | }, |
128 | }, | 128 | }, |
129 | }; | 129 | }; |
diff --git a/src/containers/settings/InviteScreen.js b/src/containers/settings/InviteScreen.js new file mode 100644 index 000000000..5f341b1b3 --- /dev/null +++ b/src/containers/settings/InviteScreen.js | |||
@@ -0,0 +1,44 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | ||
4 | |||
5 | import Invite from '../../components/auth/Invite'; | ||
6 | import { gaPage } from '../../lib/analytics'; | ||
7 | |||
8 | @inject('stores', 'actions') @observer | ||
9 | export default class InviteScreen extends Component { | ||
10 | componentDidMount() { | ||
11 | gaPage('Settings/Invite'); | ||
12 | } | ||
13 | |||
14 | componentWillUnmount() { | ||
15 | this.props.stores.user.inviteRequest.reset(); | ||
16 | } | ||
17 | |||
18 | render() { | ||
19 | const { actions } = this.props; | ||
20 | const { user } = this.props.stores; | ||
21 | |||
22 | return ( | ||
23 | <Invite | ||
24 | onSubmit={actions.user.invite} | ||
25 | isLoadingInvite={user.inviteRequest.isExecuting} | ||
26 | isInviteSuccessful={user.inviteRequest.wasExecuted && !user.inviteRequest.isError} | ||
27 | embed | ||
28 | /> | ||
29 | ); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | InviteScreen.wrappedComponent.propTypes = { | ||
34 | actions: PropTypes.shape({ | ||
35 | user: PropTypes.shape({ | ||
36 | invite: PropTypes.func.isRequired, | ||
37 | }).isRequired, | ||
38 | }).isRequired, | ||
39 | stores: PropTypes.shape({ | ||
40 | user: PropTypes.shape({ | ||
41 | inviteRequest: PropTypes.object, | ||
42 | }).isRequired, | ||
43 | }).isRequired, | ||
44 | }; | ||
diff --git a/src/containers/ui/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js index d08507809..fc6e3c4be 100644 --- a/src/containers/ui/SubscriptionFormScreen.js +++ b/src/containers/subscription/SubscriptionFormScreen.js | |||
@@ -5,7 +5,7 @@ import { inject, observer } from 'mobx-react'; | |||
5 | 5 | ||
6 | import PaymentStore from '../../stores/PaymentStore'; | 6 | import PaymentStore from '../../stores/PaymentStore'; |
7 | 7 | ||
8 | import SubscriptionForm from '../../components/ui/Subscription'; | 8 | import SubscriptionForm from '../../components/subscription/SubscriptionForm'; |
9 | 9 | ||
10 | const { BrowserWindow } = remote; | 10 | const { BrowserWindow } = remote; |
11 | 11 | ||
@@ -34,47 +34,36 @@ export default class SubscriptionFormScreen extends Component { | |||
34 | actions, | 34 | actions, |
35 | stores, | 35 | stores, |
36 | onCloseWindow, | 36 | onCloseWindow, |
37 | skipAction, | ||
38 | } = this.props; | 37 | } = this.props; |
39 | 38 | ||
40 | if (plan !== 'mining') { | 39 | const interval = plan; |
41 | const interval = plan; | ||
42 | |||
43 | const { id } = stores.payment.plan[interval]; | ||
44 | actions.payment.createHostedPage({ | ||
45 | planId: id, | ||
46 | }); | ||
47 | 40 | ||
48 | const hostedPage = await stores.payment.createHostedPageRequest; | 41 | const { id } = stores.payment.plan[interval]; |
49 | const url = `file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`; | 42 | actions.payment.createHostedPage({ |
43 | planId: id, | ||
44 | }); | ||
50 | 45 | ||
51 | if (hostedPage.url) { | 46 | const hostedPage = await stores.payment.createHostedPageRequest; |
52 | const paymentWindow = new BrowserWindow({ | 47 | const url = `file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`; |
53 | parent: remote.getCurrentWindow(), | ||
54 | modal: true, | ||
55 | title: '🔒 Franz Supporter License', | ||
56 | width: 600, | ||
57 | height: window.innerHeight - 100, | ||
58 | maxWidth: 600, | ||
59 | minWidth: 600, | ||
60 | webPreferences: { | ||
61 | nodeIntegration: true, | ||
62 | }, | ||
63 | }); | ||
64 | paymentWindow.loadURL(url); | ||
65 | 48 | ||
66 | paymentWindow.on('closed', () => { | 49 | if (hostedPage.url) { |
67 | onCloseWindow(); | 50 | const paymentWindow = new BrowserWindow({ |
68 | }); | 51 | parent: remote.getCurrentWindow(), |
69 | } | 52 | modal: true, |
70 | } else { | 53 | title: '🔒 Franz Supporter License', |
71 | actions.user.update({ | 54 | width: 600, |
72 | userData: { | 55 | height: window.innerHeight - 100, |
73 | isMiner: true, | 56 | maxWidth: 600, |
57 | minWidth: 600, | ||
58 | webPreferences: { | ||
59 | nodeIntegration: true, | ||
74 | }, | 60 | }, |
75 | }); | 61 | }); |
62 | paymentWindow.loadURL(url); | ||
76 | 63 | ||
77 | skipAction(); | 64 | paymentWindow.on('closed', () => { |
65 | onCloseWindow(); | ||
66 | }); | ||
78 | } | 67 | } |
79 | } | 68 | } |
80 | 69 | ||
diff --git a/src/containers/ui/SubscriptionPopupScreen.js b/src/containers/subscription/SubscriptionPopupScreen.js index 0b6432e50..bb0603170 100644 --- a/src/containers/ui/SubscriptionPopupScreen.js +++ b/src/containers/subscription/SubscriptionPopupScreen.js | |||
@@ -2,7 +2,7 @@ import React, { Component } from 'react'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { inject, observer } from 'mobx-react'; | 3 | import { inject, observer } from 'mobx-react'; |
4 | 4 | ||
5 | import SubscriptionPopup from '../../components/ui/SubscriptionPopup'; | 5 | import SubscriptionPopup from '../../components/subscription/SubscriptionPopup'; |
6 | 6 | ||
7 | 7 | ||
8 | @inject('stores', 'actions') @observer | 8 | @inject('stores', 'actions') @observer |
diff --git a/src/helpers/validation-helpers.js b/src/helpers/validation-helpers.js index eeb12cab7..a8a242d54 100644 --- a/src/helpers/validation-helpers.js +++ b/src/helpers/validation-helpers.js | |||
@@ -13,7 +13,7 @@ export function email({ field }) { | |||
13 | isValid = true; | 13 | isValid = true; |
14 | } | 14 | } |
15 | 15 | ||
16 | return [isValid, `${field.label} is not a valid email address`]; | 16 | return [isValid, `${field.label} not valid`]; |
17 | } | 17 | } |
18 | 18 | ||
19 | export function url({ field }) { | 19 | export function url({ field }) { |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 7fc9eac1c..d5c0ea441 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "invite.name.label": "Name", | 45 | "invite.name.label": "Name", |
46 | "invite.email.label": "Email address", | 46 | "invite.email.label": "Email address", |
47 | "invite.skip.label": "I want to do this later", | 47 | "invite.skip.label": "I want to do this later", |
48 | "invite.successInfo": "Invitations sent successfully", | ||
48 | "subscription.submit.label": "I want to support the development of Franz", | 49 | "subscription.submit.label": "I want to support the development of Franz", |
49 | "subscription.paymentSessionError": "Could not initialize payment form", | 50 | "subscription.paymentSessionError": "Could not initialize payment form", |
50 | "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes", | 51 | "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes", |
@@ -84,17 +85,15 @@ | |||
84 | "settings.account.headlinePassword": "Change password", | 85 | "settings.account.headlinePassword": "Change password", |
85 | "settings.account.successInfo": "Your changes have been saved", | 86 | "settings.account.successInfo": "Your changes have been saved", |
86 | "settings.account.buttonSave": "Update profile", | 87 | "settings.account.buttonSave": "Update profile", |
87 | "settings.account.mining.thankyou": "Thank you for supporting Franz with your processing power.", | ||
88 | "settings.account.mining.active": "You are right now performing {hashes} calculations per second.", | ||
89 | "settings.account.mining.moreInformation": "Get more information", | ||
90 | "settings.account.mining.cancel": "Cancel mining", | ||
91 | "settings.account.deleteAccount": "Delete account", | 88 | "settings.account.deleteAccount": "Delete account", |
92 | "settings.account.deleteInfo": "If you don't need your Franz account any longer, you can delete your account and all related data here.", | 89 | "settings.account.deleteInfo": "If you don't need your Franz account any longer, you can delete your account and all related data here.", |
93 | "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", | 90 | "settings.account.deleteEmailSent": "You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", |
91 | "settings.invite.headline": "Invite Friends", | ||
94 | "settings.navigation.availableServices": "Available services", | 92 | "settings.navigation.availableServices": "Available services", |
95 | "settings.navigation.yourServices": "Your services", | 93 | "settings.navigation.yourServices": "Your services", |
96 | "settings.navigation.account": "Account", | 94 | "settings.navigation.account": "Account", |
97 | "settings.navigation.settings": "Settings", | 95 | "settings.navigation.settings": "Settings", |
96 | "settings.navigation.inviteFriends": "Invite Friends", | ||
98 | "settings.navigation.logout": "Logout", | 97 | "settings.navigation.logout": "Logout", |
99 | "settings.recipes.headline": "Available services", | 98 | "settings.recipes.headline": "Available services", |
100 | "settings.recipes.mostPopular": "Most popular", | 99 | "settings.recipes.mostPopular": "Most popular", |
@@ -160,6 +159,8 @@ | |||
160 | "settings.app.form.autoLaunchInBackground": "Open in background", | 159 | "settings.app.form.autoLaunchInBackground": "Open in background", |
161 | "settings.app.form.enableSystemTray": "Show Franz in system tray", | 160 | "settings.app.form.enableSystemTray": "Show Franz in system tray", |
162 | "settings.app.form.minimizeToSystemTray": "Minimize Franz to system tray", | 161 | "settings.app.form.minimizeToSystemTray": "Minimize Franz to system tray", |
162 | "settings.app.form.enableMenuBar": "Show Franz in Menu Bar", | ||
163 | "settings.app.form.hideDockIcon": "Hide Franz icon in Dock", | ||
163 | "settings.app.form.runInBackground": "Keep Franz in background when closing the window", | 164 | "settings.app.form.runInBackground": "Keep Franz in background when closing the window", |
164 | "settings.app.form.language": "Language", | 165 | "settings.app.form.language": "Language", |
165 | "settings.app.form.enableSpellchecking": "Enable spell checking", | 166 | "settings.app.form.enableSpellchecking": "Enable spell checking", |
@@ -181,13 +182,6 @@ | |||
181 | "subscription.type.free": "free", | 182 | "subscription.type.free": "free", |
182 | "subscription.type.month": "month", | 183 | "subscription.type.month": "month", |
183 | "subscription.type.year": "year", | 184 | "subscription.type.year": "year", |
184 | "subscription.type.mining": "Support Franz with processing power", | ||
185 | "subscription.mining.headline": "How does this work?", | ||
186 | "subscription.mining.experimental": "experimental", | ||
187 | "subscription.mining.line1": "By enabling \"Support with processing power\", Franz will use about 20-50% of your CPU to mine the cryptocurrency Monero which equals approximately $ 5/year.", | ||
188 | "subscription.mining.line2": "We will adapt the CPU usage based to your work behaviour to not drain your battery and slow you and your machine down.", | ||
189 | "subscription.mining.line3": "As long as the miner is active, you will have unlimited access to all the Franz Premium Supporter Features.", | ||
190 | "subscription.mining.moreInformation": "Get more information about this plan.", | ||
191 | "subscription.euTaxInfo": "EU residents: local sales tax may apply", | 185 | "subscription.euTaxInfo": "EU residents: local sales tax may apply", |
192 | "subscriptionPopup.buttonCancel": "Cancel", | 186 | "subscriptionPopup.buttonCancel": "Cancel", |
193 | "subscriptionPopup.buttonDone": "Done", | 187 | "subscriptionPopup.buttonDone": "Done", |
diff --git a/src/lib/Form.js b/src/lib/Form.js index a22699b45..9b8321948 100644 --- a/src/lib/Form.js +++ b/src/lib/Form.js | |||
@@ -21,8 +21,9 @@ export default class DefaultForm extends Form { | |||
21 | 21 | ||
22 | options() { | 22 | options() { |
23 | return { | 23 | return { |
24 | validateOnInit: false, | 24 | validateOnInit: false, // default: true |
25 | // validateOnBlur: true, | 25 | // validateOnBlur: true, // default: true |
26 | // validateOnChange: true // default: false | ||
26 | // // validationDebounceWait: { | 27 | // // validationDebounceWait: { |
27 | // // trailing: true, | 28 | // // trailing: true, |
28 | // // }, | 29 | // // }, |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index d01666d49..703060dc1 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -22,10 +22,14 @@ const template = [ | |||
22 | role: 'cut', | 22 | role: 'cut', |
23 | }, | 23 | }, |
24 | { | 24 | { |
25 | role: 'copy', | 25 | label: 'Copy', |
26 | accelerator: 'Cmd+C', | ||
27 | selector: 'copy:', | ||
26 | }, | 28 | }, |
27 | { | 29 | { |
28 | role: 'paste', | 30 | label: 'Paste', |
31 | accelerator: 'Cmd+V', | ||
32 | selector: 'paste:', | ||
29 | }, | 33 | }, |
30 | { | 34 | { |
31 | role: 'pasteandmatchstyle', | 35 | role: 'pasteandmatchstyle', |
diff --git a/src/lib/Miner.js b/src/lib/Miner.js deleted file mode 100644 index cbf490bcb..000000000 --- a/src/lib/Miner.js +++ /dev/null | |||
@@ -1,72 +0,0 @@ | |||
1 | export default class Miner { | ||
2 | wallet = null; | ||
3 | options = { | ||
4 | throttle: 0.75, | ||
5 | throttleIdle: 0.65, | ||
6 | }; | ||
7 | miner = null; | ||
8 | interval; | ||
9 | |||
10 | constructor(wallet, options) { | ||
11 | this.wallet = wallet; | ||
12 | |||
13 | this.options = Object.assign({}, options, this.options); | ||
14 | } | ||
15 | |||
16 | start(updateFn) { | ||
17 | const script = document.createElement('script'); | ||
18 | script.id = 'coinhive'; | ||
19 | script.type = 'text/javascript'; | ||
20 | script.src = 'https://coinhive.com/lib/ch2.min.js'; | ||
21 | document.head.appendChild(script); | ||
22 | |||
23 | script.addEventListener('load', () => { | ||
24 | const miner = new window.CoinHive.Anonymous(this.wallet); | ||
25 | miner.start(); | ||
26 | miner.setThrottle(this.options.throttle); | ||
27 | |||
28 | this.miner = miner; | ||
29 | |||
30 | this.interval = setInterval(() => { | ||
31 | const hashesPerSecond = miner.getHashesPerSecond(); | ||
32 | const totalHashes = miner.getTotalHashes(); | ||
33 | const acceptedHashes = miner.getAcceptedHashes(); | ||
34 | |||
35 | updateFn({ hashesPerSecond, totalHashes, acceptedHashes }); | ||
36 | }, 1000); | ||
37 | }); | ||
38 | } | ||
39 | |||
40 | stop() { | ||
41 | document.querySelector('#coinhive'); | ||
42 | |||
43 | this.miner.stop(); | ||
44 | clearInterval(this.interval); | ||
45 | this.miner = null; | ||
46 | } | ||
47 | |||
48 | setThrottle(throttle) { | ||
49 | if (this.miner) { | ||
50 | this.miner.setThrottle(throttle); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | setActiveThrottle() { | ||
55 | if (this.miner) { | ||
56 | this.miner.setThrottle(this.options.throttle); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | async setIdleThrottle() { | ||
61 | const battery = await navigator.getBattery(); | ||
62 | |||
63 | if (!battery.charging) { | ||
64 | console.info(`Miner: battery is not charging, setThrottle to ${this.options.throttle}`); | ||
65 | this.setActiveThrottle(); | ||
66 | } else { | ||
67 | this.miner.setThrottle(this.options.throttleIdle); | ||
68 | } | ||
69 | |||
70 | return this; | ||
71 | } | ||
72 | } | ||
diff --git a/src/lib/Tray.js b/src/lib/Tray.js index 2efe71a71..588fa75bf 100644 --- a/src/lib/Tray.js +++ b/src/lib/Tray.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import { app, Tray, Menu, systemPreferences } from 'electron'; | 1 | import { app, Tray, Menu, systemPreferences, nativeImage } from 'electron'; |
2 | import path from 'path'; | 2 | import path from 'path'; |
3 | 3 | ||
4 | const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png'; | 4 | const FILE_EXTENSION = process.platform === 'win32' ? 'ico' : 'png'; |
@@ -78,8 +78,8 @@ export default class TrayIcon { | |||
78 | platform = `${platform}-dark`; | 78 | platform = `${platform}-dark`; |
79 | } | 79 | } |
80 | 80 | ||
81 | return path.join( | 81 | return nativeImage.createFromPath(path.join( |
82 | __dirname, '..', 'assets', 'images', type, platform, `${asset}.${FILE_EXTENSION}`, | 82 | __dirname, '..', 'assets', 'images', type, platform, `${asset}.${FILE_EXTENSION}`), |
83 | ); | 83 | ); |
84 | } | 84 | } |
85 | } | 85 | } |
diff --git a/src/models/Service.js b/src/models/Service.js index 423510c7d..bafb3f564 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -6,6 +6,7 @@ export default class Service { | |||
6 | id = ''; | 6 | id = ''; |
7 | recipe = ''; | 7 | recipe = ''; |
8 | webview = null; | 8 | webview = null; |
9 | timer = null; | ||
9 | events: {}; | 10 | events: {}; |
10 | 11 | ||
11 | isAttached = false; | 12 | isAttached = false; |
diff --git a/src/models/Settings.js b/src/models/Settings.js index ca44da258..e39b63087 100644 --- a/src/models/Settings.js +++ b/src/models/Settings.js | |||
@@ -2,7 +2,6 @@ import { observable, extendObservable } from 'mobx'; | |||
2 | import { DEFAULT_APP_SETTINGS } from '../config'; | 2 | import { DEFAULT_APP_SETTINGS } from '../config'; |
3 | 3 | ||
4 | export default class Settings { | 4 | export default class Settings { |
5 | @observable autoLaunchOnStart = DEFAULT_APP_SETTINGS.autoLaunchOnStart; | ||
6 | @observable autoLaunchInBackground = DEFAULT_APP_SETTINGS.autoLaunchInBackground; | 5 | @observable autoLaunchInBackground = DEFAULT_APP_SETTINGS.autoLaunchInBackground; |
7 | @observable runInBackground = DEFAULT_APP_SETTINGS.runInBackground; | 6 | @observable runInBackground = DEFAULT_APP_SETTINGS.runInBackground; |
8 | @observable enableSystemTray = DEFAULT_APP_SETTINGS.enableSystemTray; | 7 | @observable enableSystemTray = DEFAULT_APP_SETTINGS.enableSystemTray; |
diff --git a/src/models/User.js b/src/models/User.js index e2d2fc0c8..2e5df4795 100644 --- a/src/models/User.js +++ b/src/models/User.js | |||
@@ -34,8 +34,4 @@ export default class User { | |||
34 | this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner; | 34 | this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner; |
35 | this.isMiner = data.isMiner || this.isMiner; | 35 | this.isMiner = data.isMiner || this.isMiner; |
36 | } | 36 | } |
37 | |||
38 | // @computed get isPremium() { | ||
39 | // | ||
40 | // } | ||
41 | } | 37 | } |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index e33f50f05..162422017 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -3,17 +3,15 @@ import { action, computed, observable } from 'mobx'; | |||
3 | import moment from 'moment'; | 3 | import moment from 'moment'; |
4 | import key from 'keymaster'; | 4 | import key from 'keymaster'; |
5 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; | 5 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; |
6 | import idleTimer from '@paulcbetts/system-idle-time'; | ||
7 | import AutoLaunch from 'auto-launch'; | 6 | import AutoLaunch from 'auto-launch'; |
8 | import prettyBytes from 'pretty-bytes'; | 7 | import prettyBytes from 'pretty-bytes'; |
9 | 8 | ||
10 | import Store from './lib/Store'; | 9 | import Store from './lib/Store'; |
11 | import Request from './lib/Request'; | 10 | import Request from './lib/Request'; |
12 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; | 11 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; |
13 | import { isMac } from '../environment'; | 12 | import { isMac, isLinux, isWindows } from '../environment'; |
14 | import locales from '../i18n/translations'; | 13 | import locales from '../i18n/translations'; |
15 | import { gaEvent } from '../lib/analytics'; | 14 | import { gaEvent } from '../lib/analytics'; |
16 | import Miner from '../lib/Miner'; | ||
17 | 15 | ||
18 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 16 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
19 | 17 | ||
@@ -46,11 +44,6 @@ export default class AppStore extends Store { | |||
46 | 44 | ||
47 | @observable locale = defaultLocale; | 45 | @observable locale = defaultLocale; |
48 | 46 | ||
49 | @observable idleTime = 0; | ||
50 | |||
51 | miner = null; | ||
52 | @observable minerHashrate = 0.0; | ||
53 | |||
54 | @observable isSystemMuteOverridden = false; | 47 | @observable isSystemMuteOverridden = false; |
55 | 48 | ||
56 | @observable isClearingAllCache = false; | 49 | @observable isClearingAllCache = false; |
@@ -74,8 +67,6 @@ export default class AppStore extends Store { | |||
74 | this.registerReactions([ | 67 | this.registerReactions([ |
75 | this._offlineCheck.bind(this), | 68 | this._offlineCheck.bind(this), |
76 | this._setLocale.bind(this), | 69 | this._setLocale.bind(this), |
77 | this._handleMiner.bind(this), | ||
78 | this._handleMinerThrottle.bind(this), | ||
79 | this._muteAppHandler.bind(this), | 70 | this._muteAppHandler.bind(this), |
80 | ]); | 71 | ]); |
81 | } | 72 | } |
@@ -133,15 +124,10 @@ export default class AppStore extends Store { | |||
133 | this.stores.router.push(data.url); | 124 | this.stores.router.push(data.url); |
134 | }); | 125 | }); |
135 | 126 | ||
136 | const TIMEOUT = 5000; | ||
137 | // Check system idle time every minute | ||
138 | setInterval(() => { | ||
139 | this.idleTime = idleTimer.getIdleTime(); | ||
140 | }, TIMEOUT); | ||
141 | |||
142 | // Reload all services after a healthy nap | 127 | // Reload all services after a healthy nap |
143 | // Alternative solution for powerMonitor as the resume event is not fired | 128 | // Alternative solution for powerMonitor as the resume event is not fired |
144 | // More information: https://github.com/electron/electron/issues/1615 | 129 | // More information: https://github.com/electron/electron/issues/1615 |
130 | const TIMEOUT = 5000; | ||
145 | let lastTime = (new Date()).getTime(); | 131 | let lastTime = (new Date()).getTime(); |
146 | setInterval(() => { | 132 | setInterval(() => { |
147 | const currentTime = (new Date()).getTime(); | 133 | const currentTime = (new Date()).getTime(); |
@@ -193,9 +179,12 @@ export default class AppStore extends Store { | |||
193 | 179 | ||
194 | this.actions.service.setActive({ serviceId }); | 180 | this.actions.service.setActive({ serviceId }); |
195 | 181 | ||
196 | if (!isMac) { | 182 | const mainWindow = remote.getCurrentWindow(); |
197 | const mainWindow = remote.getCurrentWindow(); | 183 | |
184 | if (isWindows) { | ||
198 | mainWindow.restore(); | 185 | mainWindow.restore(); |
186 | } else if (isLinux) { | ||
187 | mainWindow.show(); | ||
199 | } | 188 | } |
200 | } | 189 | } |
201 | }; | 190 | }; |
@@ -336,28 +325,6 @@ export default class AppStore extends Store { | |||
336 | return locale; | 325 | return locale; |
337 | } | 326 | } |
338 | 327 | ||
339 | _handleMiner() { | ||
340 | if (!this.stores.user.isLoggedIn) return; | ||
341 | |||
342 | if (this.stores.user.data.isMiner) { | ||
343 | this.miner = new Miner('cVO1jVkBWuIJkyqlcEHRTScAfQwaEmuH'); | ||
344 | this.miner.start(({ hashesPerSecond }) => { | ||
345 | this.minerHashrate = hashesPerSecond; | ||
346 | }); | ||
347 | } else if (this.miner) { | ||
348 | this.miner.stop(); | ||
349 | this.miner = 0; | ||
350 | } | ||
351 | } | ||
352 | |||
353 | _handleMinerThrottle() { | ||
354 | if (this.idleTime > 300000) { | ||
355 | if (this.miner) this.miner.setIdleThrottle(); | ||
356 | } else { | ||
357 | if (this.miner) this.miner.setActiveThrottle(); // eslint-disable-line | ||
358 | } | ||
359 | } | ||
360 | |||
361 | _muteAppHandler() { | 328 | _muteAppHandler() { |
362 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; | 329 | const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; |
363 | 330 | ||
@@ -367,10 +334,7 @@ export default class AppStore extends Store { | |||
367 | } | 334 | } |
368 | 335 | ||
369 | // Helpers | 336 | // Helpers |
370 | async _appStartsCounter() { | 337 | _appStartsCounter() { |
371 | // we need to wait until the settings request is resolved | ||
372 | await this.stores.settings.allSettingsRequest; | ||
373 | |||
374 | this.actions.settings.update({ | 338 | this.actions.settings.update({ |
375 | settings: { | 339 | settings: { |
376 | appStarts: (this.stores.settings.all.appStarts || 0) + 1, | 340 | appStarts: (this.stores.settings.all.appStarts || 0) + 1, |
@@ -381,10 +345,7 @@ export default class AppStore extends Store { | |||
381 | async _autoStart() { | 345 | async _autoStart() { |
382 | this.autoLaunchOnStart = await this._checkAutoStart(); | 346 | this.autoLaunchOnStart = await this._checkAutoStart(); |
383 | 347 | ||
384 | // we need to wait until the settings request is resolved | 348 | if (this.stores.settings.all.appStarts === 1) { |
385 | await this.stores.settings.allSettingsRequest; | ||
386 | |||
387 | if (!this.stores.settings.all.appStarts) { | ||
388 | this.actions.app.launchOnStartup({ | 349 | this.actions.app.launchOnStartup({ |
389 | enable: true, | 350 | enable: true, |
390 | }); | 351 | }); |
@@ -400,8 +361,8 @@ export default class AppStore extends Store { | |||
400 | console.debug('reactivateServices: computer is offline, trying again in 5s, retries:', retryCount); | 361 | console.debug('reactivateServices: computer is offline, trying again in 5s, retries:', retryCount); |
401 | setTimeout(() => this._reactivateServices(retryCount + 1), 5000); | 362 | setTimeout(() => this._reactivateServices(retryCount + 1), 5000); |
402 | } else { | 363 | } else { |
403 | console.debug('reactivateServices: reload all services'); | 364 | console.debug('reactivateServices: reload Franz'); |
404 | this.actions.service.reloadAll(); | 365 | window.location.reload(); |
405 | } | 366 | } |
406 | } | 367 | } |
407 | 368 | ||
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 7300a76c8..c38d0d9ee 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -187,13 +187,13 @@ export default class ServicesStore extends Store { | |||
187 | 187 | ||
188 | // patch custom icon deletion | 188 | // patch custom icon deletion |
189 | if (data.customIcon === 'delete') { | 189 | if (data.customIcon === 'delete') { |
190 | data.iconUrl = ''; | 190 | newData.iconUrl = ''; |
191 | data.hasCustomUploadedIcon = false; | 191 | newData.hasCustomUploadedIcon = false; |
192 | } | 192 | } |
193 | 193 | ||
194 | // patch custom icon url | 194 | // patch custom icon url |
195 | if (data.customIconUrl) { | 195 | if (data.customIconUrl) { |
196 | data.iconUrl = data.customIconUrl; | 196 | newData.iconUrl = data.customIconUrl; |
197 | } | 197 | } |
198 | 198 | ||
199 | Object.assign(result.find(c => c.id === serviceId), newData); | 199 | Object.assign(result.find(c => c.id === serviceId), newData); |
@@ -536,7 +536,6 @@ export default class ServicesStore extends Store { | |||
536 | 536 | ||
537 | // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases | 537 | // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases |
538 | if (showMessageBadgesEvenWhenMuted) { | 538 | if (showMessageBadgesEvenWhenMuted) { |
539 | console.log('set badge', unreadDirectMessageCount, unreadIndirectMessageCount); | ||
540 | this.actions.app.setBadge({ | 539 | this.actions.app.setBadge({ |
541 | unreadDirectMessageCount, | 540 | unreadDirectMessageCount, |
542 | unreadIndirectMessageCount, | 541 | unreadIndirectMessageCount, |
@@ -589,12 +588,16 @@ export default class ServicesStore extends Store { | |||
589 | const delay = 1000; | 588 | const delay = 1000; |
590 | 589 | ||
591 | if (service) { | 590 | if (service) { |
591 | if (service.timer !== null) { | ||
592 | clearTimeout(service.timer); | ||
593 | } | ||
594 | |||
592 | const loop = () => { | 595 | const loop = () => { |
593 | if (!service.webview) return; | 596 | if (!service.webview) return; |
594 | 597 | ||
595 | service.webview.send('poll'); | 598 | service.webview.send('poll'); |
596 | 599 | ||
597 | setTimeout(loop, delay); | 600 | service.timer = setTimeout(loop, delay); |
598 | }; | 601 | }; |
599 | 602 | ||
600 | loop(); | 603 | loop(); |
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js index da99a720f..b7d803398 100644 --- a/src/stores/SettingsStore.js +++ b/src/stores/SettingsStore.js | |||
@@ -1,17 +1,12 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { ipcRenderer } from 'electron'; |
2 | import { action, computed, observable, extendObservable } from 'mobx'; | 2 | import { action, computed } from 'mobx'; |
3 | import localStorage from 'mobx-localstorage'; | ||
3 | 4 | ||
4 | import Store from './lib/Store'; | 5 | import Store from './lib/Store'; |
5 | import Request from './lib/Request'; | ||
6 | import CachedRequest from './lib/CachedRequest'; | ||
7 | import { gaEvent } from '../lib/analytics'; | 6 | import { gaEvent } from '../lib/analytics'; |
8 | import SettingsModel from '../models/Settings'; | 7 | import SettingsModel from '../models/Settings'; |
9 | 8 | ||
10 | export default class SettingsStore extends Store { | 9 | export default class SettingsStore extends Store { |
11 | @observable allSettingsRequest = new CachedRequest(this.api.local, 'getSettings'); | ||
12 | @observable updateSettingsRequest = new Request(this.api.local, 'updateSettings'); | ||
13 | @observable removeSettingsKeyRequest = new Request(this.api.local, 'removeKey'); | ||
14 | |||
15 | constructor(...args) { | 10 | constructor(...args) { |
16 | super(...args); | 11 | super(...args); |
17 | 12 | ||
@@ -21,20 +16,16 @@ export default class SettingsStore extends Store { | |||
21 | } | 16 | } |
22 | 17 | ||
23 | setup() { | 18 | setup() { |
24 | this.allSettingsRequest.execute(); | ||
25 | this._shareSettingsWithMainProcess(); | 19 | this._shareSettingsWithMainProcess(); |
26 | } | 20 | } |
27 | 21 | ||
28 | @computed get all() { | 22 | @computed get all() { |
29 | return new SettingsModel(this.allSettingsRequest.result); | 23 | return new SettingsModel(localStorage.getItem('app') || {}); |
30 | } | 24 | } |
31 | 25 | ||
32 | @action async _update({ settings }) { | 26 | @action async _update({ settings }) { |
33 | await this.updateSettingsRequest.execute(settings)._promise; | 27 | const appSettings = this.all; |
34 | await this.allSettingsRequest.patch((result) => { | 28 | localStorage.setItem('app', Object.assign(appSettings, settings)); |
35 | if (!result) return; | ||
36 | extendObservable(result, settings); | ||
37 | }); | ||
38 | 29 | ||
39 | // We need a little hack to wait until everything is patched | 30 | // We need a little hack to wait until everything is patched |
40 | setTimeout(() => this._shareSettingsWithMainProcess(), 0); | 31 | setTimeout(() => this._shareSettingsWithMainProcess(), 0); |
@@ -43,8 +34,11 @@ export default class SettingsStore extends Store { | |||
43 | } | 34 | } |
44 | 35 | ||
45 | @action async _remove({ key }) { | 36 | @action async _remove({ key }) { |
46 | await this.removeSettingsKeyRequest.execute(key); | 37 | const appSettings = this.all; |
47 | await this.allSettingsRequest.invalidate({ immediately: true }); | 38 | if (Object.hasOwnProperty.call(appSettings, key)) { |
39 | delete appSettings[key]; | ||
40 | localStorage.setItem('app', appSettings); | ||
41 | } | ||
48 | 42 | ||
49 | this._shareSettingsWithMainProcess(); | 43 | this._shareSettingsWithMainProcess(); |
50 | } | 44 | } |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 09000dcdb..7dbbd955b 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -1,7 +1,9 @@ | |||
1 | import { observable, computed, action } from 'mobx'; | 1 | import { observable, computed, action } from 'mobx'; |
2 | import moment from 'moment'; | 2 | import moment from 'moment'; |
3 | import jwt from 'jsonwebtoken'; | 3 | import jwt from 'jsonwebtoken'; |
4 | import localStorage from 'mobx-localstorage'; | ||
4 | 5 | ||
6 | import { isDevMode } from '../environment'; | ||
5 | import Store from './lib/Store'; | 7 | import Store from './lib/Store'; |
6 | import Request from './lib/Request'; | 8 | import Request from './lib/Request'; |
7 | import CachedRequest from './lib/CachedRequest'; | 9 | import CachedRequest from './lib/CachedRequest'; |
@@ -98,7 +100,7 @@ export default class UserStore extends Store { | |||
98 | 100 | ||
99 | // Data | 101 | // Data |
100 | @computed get isLoggedIn() { | 102 | @computed get isLoggedIn() { |
101 | return this.authToken !== null && this.authToken !== undefined; | 103 | return Boolean(localStorage.getItem('authToken')); |
102 | } | 104 | } |
103 | 105 | ||
104 | // @computed get isTokenValid() { | 106 | // @computed get isTokenValid() { |
@@ -160,13 +162,17 @@ export default class UserStore extends Store { | |||
160 | gaEvent('User', 'retrievePassword'); | 162 | gaEvent('User', 'retrievePassword'); |
161 | } | 163 | } |
162 | 164 | ||
163 | @action _invite({ invites }) { | 165 | @action async _invite({ invites }) { |
164 | const data = invites.filter(invite => invite.email !== ''); | 166 | const data = invites.filter(invite => invite.email !== ''); |
165 | 167 | ||
166 | this.inviteRequest.execute(data); | 168 | const response = await this.inviteRequest.execute(data)._promise; |
167 | 169 | ||
168 | // we do not wait for a server response before redirecting the user | 170 | this.actionStatus = response.status || []; |
169 | this.stores.router.push('/'); | 171 | |
172 | // we do not wait for a server response before redirecting the user ONLY DURING SIGNUP | ||
173 | if (this.stores.router.location.pathname.includes(this.INVITE_ROUTE)) { | ||
174 | this.stores.router.push('/'); | ||
175 | } | ||
170 | 176 | ||
171 | gaEvent('User', 'inviteUsers'); | 177 | gaEvent('User', 'inviteUsers'); |
172 | } | 178 | } |
@@ -237,7 +243,9 @@ export default class UserStore extends Store { | |||
237 | && currentRoute.includes(this.BASE_ROUTE) | 243 | && currentRoute.includes(this.BASE_ROUTE) |
238 | && (this.hasCompletedSignup | 244 | && (this.hasCompletedSignup |
239 | || this.hasCompletedSignup === null)) { | 245 | || this.hasCompletedSignup === null)) { |
240 | this.stores.router.push('/'); | 246 | if (!isDevMode) { |
247 | this.stores.router.push('/'); | ||
248 | } | ||
241 | } | 249 | } |
242 | }; | 250 | }; |
243 | 251 | ||
diff --git a/src/styles/invite.scss b/src/styles/invite.scss new file mode 100644 index 000000000..bfb1a4b6b --- /dev/null +++ b/src/styles/invite.scss | |||
@@ -0,0 +1,15 @@ | |||
1 | .invite__form { | ||
2 | /* play with values to see different layouts */ | ||
3 | // display: flex; | ||
4 | align-items: center; | ||
5 | align-self: center; | ||
6 | justify-content: center; | ||
7 | } | ||
8 | |||
9 | .invite__embed { | ||
10 | text-align: center; | ||
11 | } | ||
12 | |||
13 | .invite__embed--button { | ||
14 | width: 100%; | ||
15 | } \ No newline at end of file | ||
diff --git a/src/styles/main.scss b/src/styles/main.scss index 261396f6f..446bdca14 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss | |||
@@ -27,6 +27,7 @@ $mdi-font-path: '../node_modules/mdi/fonts'; | |||
27 | @import './subscription.scss'; | 27 | @import './subscription.scss'; |
28 | @import './subscription-popup.scss'; | 28 | @import './subscription-popup.scss'; |
29 | @import './content-tabs.scss'; | 29 | @import './content-tabs.scss'; |
30 | @import './invite.scss'; | ||
30 | 31 | ||
31 | // form | 32 | // form |
32 | @import './input.scss'; | 33 | @import './input.scss'; |
diff --git a/src/styles/subscription.scss b/src/styles/subscription.scss index 01d8f4ecb..8bfb68d23 100644 --- a/src/styles/subscription.scss +++ b/src/styles/subscription.scss | |||
@@ -63,11 +63,3 @@ | |||
63 | } | 63 | } |
64 | } | 64 | } |
65 | } | 65 | } |
66 | |||
67 | .mining-details { | ||
68 | margin-bottom: 15px; | ||
69 | |||
70 | button { | ||
71 | color: $theme-brand-primary; | ||
72 | } | ||
73 | } | ||
diff --git a/src/styles/welcome.scss b/src/styles/welcome.scss index 46299b966..a12069ba4 100644 --- a/src/styles/welcome.scss +++ b/src/styles/welcome.scss | |||
@@ -55,6 +55,15 @@ | |||
55 | background: #FFF; | 55 | background: #FFF; |
56 | color: $theme-brand-primary; | 56 | color: $theme-brand-primary; |
57 | } | 57 | } |
58 | |||
59 | &__inverted { | ||
60 | background: #FFF; | ||
61 | color: $theme-brand-primary; | ||
62 | } | ||
63 | &__inverted:hover { | ||
64 | background: none; | ||
65 | color: #FFF; | ||
66 | } | ||
58 | } | 67 | } |
59 | 68 | ||
60 | &__featured-services { | 69 | &__featured-services { |