summaryrefslogtreecommitdiffstats
path: root/src/containers
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
commit58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch)
tree1211600c2a5d3b5f81c435c6896618111a611720 /src/containers
downloadferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip
initial commit
Diffstat (limited to 'src/containers')
-rw-r--r--src/containers/auth/AuthLayoutContainer.js47
-rw-r--r--src/containers/auth/ImportScreen.js41
-rw-r--r--src/containers/auth/InviteScreen.js29
-rw-r--r--src/containers/auth/LoginScreen.js45
-rw-r--r--src/containers/auth/PasswordScreen.js38
-rw-r--r--src/containers/auth/PricingScreen.js53
-rw-r--r--src/containers/auth/SignupScreen.js43
-rw-r--r--src/containers/auth/WelcomeScreen.js34
-rw-r--r--src/containers/layout/AppLayoutContainer.js166
-rw-r--r--src/containers/settings/AccountScreen.js114
-rw-r--r--src/containers/settings/EditServiceScreen.js208
-rw-r--r--src/containers/settings/EditSettingsScreen.js167
-rw-r--r--src/containers/settings/EditUserScreen.js165
-rw-r--r--src/containers/settings/RecipesScreen.js126
-rw-r--r--src/containers/settings/ServicesScreen.js75
-rw-r--r--src/containers/settings/SettingsWindow.js43
-rw-r--r--src/containers/ui/SubscriptionFormScreen.js126
-rw-r--r--src/containers/ui/SubscriptionPopupScreen.js43
18 files changed, 1563 insertions, 0 deletions
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
new file mode 100644
index 000000000..004054fdd
--- /dev/null
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -0,0 +1,47 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import AuthLayout from '../../components/auth/AuthLayout';
6import AppStore from '../../stores/AppStore';
7import GlobalErrorStore from '../../stores/GlobalErrorStore';
8
9import { oneOrManyChildElements } from '../../prop-types';
10
11@inject('stores', 'actions') @observer
12export default class AuthLayoutContainer extends Component {
13 static propTypes = {
14 children: oneOrManyChildElements.isRequired,
15 location: PropTypes.shape({
16 pathname: PropTypes.string.isRequired,
17 }).isRequired,
18 };
19
20 render() {
21 const { stores, actions, children, location } = this.props;
22 return (
23 <AuthLayout
24 error={stores.globalError.response}
25 pathname={location.pathname}
26 isOnline={stores.app.isOnline}
27 isAPIHealthy={!stores.app.healthCheckRequest.isError}
28 retryHealthCheck={actions.app.healthCheck}
29 isHealthCheckLoading={stores.app.healthCheckRequest.isExecuting}
30 >
31 {children}
32 </AuthLayout>
33 );
34 }
35}
36
37AuthLayoutContainer.wrappedComponent.propTypes = {
38 stores: PropTypes.shape({
39 app: PropTypes.instanceOf(AppStore).isRequired,
40 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
41 }).isRequired,
42 actions: PropTypes.shape({
43 app: PropTypes.shape({
44 healthCheck: PropTypes.func.isRequired,
45 }).isRequired,
46 }).isRequired,
47};
diff --git a/src/containers/auth/ImportScreen.js b/src/containers/auth/ImportScreen.js
new file mode 100644
index 000000000..ddd56ffb6
--- /dev/null
+++ b/src/containers/auth/ImportScreen.js
@@ -0,0 +1,41 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Import from '../../components/auth/Import';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8@inject('stores', 'actions') @observer
9export default class ImportScreen extends Component {
10 componentDidMount() {
11 gaPage('Auth/Import');
12 }
13
14 render() {
15 const { actions, stores } = this.props;
16
17 if (stores.user.isImportLegacyServicesCompleted) {
18 stores.router.push(stores.user.inviteRoute);
19 }
20
21 return (
22 <Import
23 services={stores.user.legacyServices}
24 onSubmit={actions.user.importLegacyServices}
25 isSubmitting={stores.user.isImportLegacyServicesExecuting}
26 inviteRoute={stores.user.inviteRoute}
27 />
28 );
29 }
30}
31
32ImportScreen.wrappedComponent.propTypes = {
33 actions: PropTypes.shape({
34 user: PropTypes.shape({
35 importLegacyServices: PropTypes.func.isRequired,
36 }).isRequired,
37 }).isRequired,
38 stores: PropTypes.shape({
39 user: PropTypes.instanceOf(UserStore).isRequired,
40 }).isRequired,
41};
diff --git a/src/containers/auth/InviteScreen.js b/src/containers/auth/InviteScreen.js
new file mode 100644
index 000000000..51971f436
--- /dev/null
+++ b/src/containers/auth/InviteScreen.js
@@ -0,0 +1,29 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Invite from '../../components/auth/Invite';
5import { gaPage } from '../../lib/analytics';
6
7@inject('stores', 'actions') @observer
8export default class InviteScreen extends Component {
9 componentDidMount() {
10 gaPage('Auth/Invite');
11 }
12
13 render() {
14 const { actions } = this.props;
15 return (
16 <Invite
17 onSubmit={actions.user.invite}
18 />
19 );
20 }
21}
22
23InviteScreen.wrappedComponent.propTypes = {
24 actions: PropTypes.shape({
25 user: PropTypes.shape({
26 invite: PropTypes.func.isRequired,
27 }).isRequired,
28 }).isRequired,
29};
diff --git a/src/containers/auth/LoginScreen.js b/src/containers/auth/LoginScreen.js
new file mode 100644
index 000000000..9e22c5141
--- /dev/null
+++ b/src/containers/auth/LoginScreen.js
@@ -0,0 +1,45 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Login from '../../components/auth/Login';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8import { globalError as globalErrorPropType } from '../../prop-types';
9
10@inject('stores', 'actions') @observer
11export default class LoginScreen extends Component {
12 static propTypes = {
13 error: globalErrorPropType.isRequired,
14 };
15
16 componentDidMount() {
17 gaPage('Auth/Login');
18 }
19
20 render() {
21 const { actions, stores, error } = this.props;
22 return (
23 <Login
24 onSubmit={actions.user.login}
25 isSubmitting={stores.user.loginRequest.isExecuting}
26 isTokenExpired={stores.user.isTokenExpired}
27 isServerLogout={stores.user.logoutReason === stores.user.logoutReasonTypes.SERVER}
28 signupRoute={stores.user.signupRoute}
29 passwordRoute={stores.user.passwordRoute}
30 error={error}
31 />
32 );
33 }
34}
35
36LoginScreen.wrappedComponent.propTypes = {
37 actions: PropTypes.shape({
38 user: PropTypes.shape({
39 login: PropTypes.func.isRequired,
40 }).isRequired,
41 }).isRequired,
42 stores: PropTypes.shape({
43 user: PropTypes.instanceOf(UserStore).isRequired,
44 }).isRequired,
45};
diff --git a/src/containers/auth/PasswordScreen.js b/src/containers/auth/PasswordScreen.js
new file mode 100644
index 000000000..d88cb08e6
--- /dev/null
+++ b/src/containers/auth/PasswordScreen.js
@@ -0,0 +1,38 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import Password from '../../components/auth/Password';
5import UserStore from '../../stores/UserStore';
6import { gaPage } from '../../lib/analytics';
7
8@inject('stores', 'actions') @observer
9export default class PasswordScreen extends Component {
10 componentDidMount() {
11 gaPage('Auth/Password Retrieve');
12 }
13
14 render() {
15 const { actions, stores } = this.props;
16
17 return (
18 <Password
19 onSubmit={actions.user.retrievePassword}
20 isSubmitting={stores.user.passwordRequest.isExecuting}
21 signupRoute={stores.user.signupRoute}
22 loginRoute={stores.user.loginRoute}
23 status={stores.user.actionStatus}
24 />
25 );
26 }
27}
28
29PasswordScreen.wrappedComponent.propTypes = {
30 actions: PropTypes.shape({
31 user: PropTypes.shape({
32 retrievePassword: PropTypes.func.isRequired,
33 }).isRequired,
34 }).isRequired,
35 stores: PropTypes.shape({
36 user: PropTypes.instanceOf(UserStore).isRequired,
37 }).isRequired,
38};
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js
new file mode 100644
index 000000000..7e1586535
--- /dev/null
+++ b/src/containers/auth/PricingScreen.js
@@ -0,0 +1,53 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5
6import Pricing from '../../components/auth/Pricing';
7import UserStore from '../../stores/UserStore';
8import PaymentStore from '../../stores/PaymentStore';
9import { gaPage } from '../../lib/analytics';
10
11import { globalError as globalErrorPropType } from '../../prop-types';
12
13@inject('stores', 'actions') @observer
14export default class PricingScreen extends Component {
15 static propTypes = {
16 error: globalErrorPropType.isRequired,
17 };
18
19 componentDidMount() {
20 gaPage('Auth/Pricing');
21 }
22
23 render() {
24 const { actions, stores, error } = this.props;
25
26 const nextStepRoute = stores.user.legacyServices.length ? stores.user.importRoute : stores.user.inviteRoute;
27
28 return (
29 <Pricing
30 donor={stores.user.data.donor || {}}
31 onSubmit={actions.user.signup}
32 onCloseSubscriptionWindow={() => this.props.stores.router.push(nextStepRoute)}
33 isLoading={stores.payment.plansRequest.isExecuting}
34 isLoadingUser={stores.user.getUserInfoRequest.isExecuting}
35 error={error}
36 skipAction={() => this.props.stores.router.push(nextStepRoute)}
37 />
38 );
39 }
40}
41
42PricingScreen.wrappedComponent.propTypes = {
43 actions: PropTypes.shape({
44 user: PropTypes.shape({
45 signup: PropTypes.func.isRequired,
46 }).isRequired,
47 }).isRequired,
48 stores: PropTypes.shape({
49 user: PropTypes.instanceOf(UserStore).isRequired,
50 payment: PropTypes.instanceOf(PaymentStore).isRequired,
51 router: PropTypes.instanceOf(RouterStore).isRequired,
52 }).isRequired,
53};
diff --git a/src/containers/auth/SignupScreen.js b/src/containers/auth/SignupScreen.js
new file mode 100644
index 000000000..3b86ab138
--- /dev/null
+++ b/src/containers/auth/SignupScreen.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import Signup from '../../components/auth/Signup';
6import UserStore from '../../stores/UserStore';
7import { gaPage } from '../../lib/analytics';
8
9import { globalError as globalErrorPropType } from '../../prop-types';
10
11@inject('stores', 'actions') @observer
12export default class SignupScreen extends Component {
13 static propTypes = {
14 error: globalErrorPropType.isRequired,
15 };
16
17 componentDidMount() {
18 gaPage('Auth/Signup');
19 }
20
21 render() {
22 const { actions, stores, error } = this.props;
23 return (
24 <Signup
25 onSubmit={actions.user.signup}
26 isSubmitting={stores.user.signupRequest.isExecuting}
27 loginRoute={stores.user.loginRoute}
28 error={error}
29 />
30 );
31 }
32}
33
34SignupScreen.wrappedComponent.propTypes = {
35 actions: PropTypes.shape({
36 user: PropTypes.shape({
37 signup: PropTypes.func.isRequired,
38 }).isRequired,
39 }).isRequired,
40 stores: PropTypes.shape({
41 user: PropTypes.instanceOf(UserStore).isRequired,
42 }).isRequired,
43};
diff --git a/src/containers/auth/WelcomeScreen.js b/src/containers/auth/WelcomeScreen.js
new file mode 100644
index 000000000..e413264a6
--- /dev/null
+++ b/src/containers/auth/WelcomeScreen.js
@@ -0,0 +1,34 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import Welcome from '../../components/auth/Welcome';
6import UserStore from '../../stores/UserStore';
7import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
8import { gaPage } from '../../lib/analytics';
9
10@inject('stores', 'actions') @observer
11export default class LoginScreen extends Component {
12 componentDidMount() {
13 gaPage('Auth/Welcome');
14 }
15
16 render() {
17 const { user, recipePreviews } = this.props.stores;
18
19 return (
20 <Welcome
21 loginRoute={user.loginRoute}
22 signupRoute={user.signupRoute}
23 recipes={recipePreviews.featured}
24 />
25 );
26 }
27}
28
29LoginScreen.wrappedComponent.propTypes = {
30 stores: PropTypes.shape({
31 user: PropTypes.instanceOf(UserStore).isRequired,
32 recipePreviews: PropTypes.instanceOf(RecipePreviewsStore).isRequired,
33 }).isRequired,
34};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
new file mode 100644
index 000000000..aa7f7952a
--- /dev/null
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -0,0 +1,166 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import AppStore from '../../stores/AppStore';
6import RecipesStore from '../../stores/RecipesStore';
7import ServicesStore from '../../stores/ServicesStore';
8import UIStore from '../../stores/UIStore';
9import NewsStore from '../../stores/NewsStore';
10import UserStore from '../../stores/UserStore';
11import RequestStore from '../../stores/RequestStore';
12import GlobalErrorStore from '../../stores/GlobalErrorStore';
13
14import { oneOrManyChildElements } from '../../prop-types';
15import AppLayout from '../../components/layout/AppLayout';
16import Sidebar from '../../components/layout/Sidebar';
17import Services from '../../components/services/content/Services';
18import AppLoader from '../../components/ui/AppLoader';
19
20@inject('stores', 'actions') @observer
21export default class AppLayoutContainer extends Component {
22 static defaultProps = {
23 children: null,
24 };
25
26 render() {
27 const {
28 app,
29 services,
30 ui,
31 news,
32 globalError,
33 user,
34 requests,
35 } = this.props.stores;
36
37 const {
38 setActive,
39 handleIPCMessage,
40 setWebviewReference,
41 openWindow,
42 reloadUpdatedServices,
43 reorder,
44 reload,
45 toggleNotifications,
46 deleteService,
47 updateService,
48 } = this.props.actions.service;
49
50 const { hide } = this.props.actions.news;
51
52 const { retryRequiredRequests } = this.props.actions.requests;
53
54 const {
55 installUpdate,
56 } = this.props.actions.app;
57
58 const {
59 openSettings,
60 closeSettings,
61 } = this.props.actions.ui;
62
63 const { children } = this.props;
64 const allServices = services.enabled;
65
66 const isLoadingServices = services.allServicesRequest.isExecuting
67 && services.allServicesRequest.isExecutingFirstTime;
68
69 // const isLoadingRecipes = recipes.allRecipesRequest.isExecuting
70 // && recipes.allRecipesRequest.isExecutingFirstTime;
71
72 if (isLoadingServices) {
73 return (
74 <AppLoader />
75 );
76 }
77
78 const sidebar = (
79 <Sidebar
80 services={allServices}
81 setActive={setActive}
82 openSettings={openSettings}
83 closeSettings={closeSettings}
84 reorder={reorder}
85 reload={reload}
86 toggleNotifications={toggleNotifications}
87 deleteService={deleteService}
88 updateService={updateService}
89 isPremiumUser={user.data.isPremium}
90 />
91 );
92
93 const servicesContainer = (
94 <Services
95 // settings={allSettings}
96 services={allServices}
97 handleIPCMessage={handleIPCMessage}
98 setWebviewReference={setWebviewReference}
99 openWindow={openWindow}
100 />
101 );
102
103 return (
104 <AppLayout
105 isOnline={app.isOnline}
106 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar}
107 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
108 sidebar={sidebar}
109 services={servicesContainer}
110 news={news.latest}
111 removeNewsItem={hide}
112 reloadServicesAfterUpdate={reloadUpdatedServices}
113 installAppUpdate={installUpdate}
114 globalError={globalError.error}
115 showRequiredRequestsError={requests.showRequiredRequestsError}
116 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful}
117 retryRequiredRequests={retryRequiredRequests}
118 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
119 >
120 {React.Children.count(children) > 0 ? children : null}
121 </AppLayout>
122 );
123 }
124}
125
126AppLayoutContainer.wrappedComponent.propTypes = {
127 stores: PropTypes.shape({
128 services: PropTypes.instanceOf(ServicesStore).isRequired,
129 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
130 app: PropTypes.instanceOf(AppStore).isRequired,
131 ui: PropTypes.instanceOf(UIStore).isRequired,
132 news: PropTypes.instanceOf(NewsStore).isRequired,
133 user: PropTypes.instanceOf(UserStore).isRequired,
134 requests: PropTypes.instanceOf(RequestStore).isRequired,
135 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
136 }).isRequired,
137 actions: PropTypes.shape({
138 service: PropTypes.shape({
139 setActive: PropTypes.func.isRequired,
140 reload: PropTypes.func.isRequired,
141 toggleNotifications: PropTypes.func.isRequired,
142 handleIPCMessage: PropTypes.func.isRequired,
143 setWebviewReference: PropTypes.func.isRequired,
144 openWindow: PropTypes.func.isRequired,
145 reloadUpdatedServices: PropTypes.func.isRequired,
146 updateService: PropTypes.func.isRequired,
147 deleteService: PropTypes.func.isRequired,
148 reorder: PropTypes.func.isRequired,
149 }).isRequired,
150 news: PropTypes.shape({
151 hide: PropTypes.func.isRequired,
152 }).isRequired,
153 ui: PropTypes.shape({
154 openSettings: PropTypes.func.isRequired,
155 closeSettings: PropTypes.func.isRequired,
156 }).isRequired,
157 app: PropTypes.shape({
158 installUpdate: PropTypes.func.isRequired,
159 healthCheck: PropTypes.func.isRequired,
160 }).isRequired,
161 requests: PropTypes.shape({
162 retryRequiredRequests: PropTypes.func.isRequired,
163 }).isRequired,
164 }).isRequired,
165 children: oneOrManyChildElements,
166};
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
new file mode 100644
index 000000000..a1ac8bda3
--- /dev/null
+++ b/src/containers/settings/AccountScreen.js
@@ -0,0 +1,114 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5
6import PaymentStore from '../../stores/PaymentStore';
7import UserStore from '../../stores/UserStore';
8import AppStore from '../../stores/AppStore';
9import { gaPage } from '../../lib/analytics';
10
11import AccountDashboard from '../../components/settings/account/AccountDashboard';
12
13const { BrowserWindow } = remote;
14
15@inject('stores', 'actions') @observer
16export default class AccountScreen extends Component {
17 componentDidMount() {
18 gaPage('Settings/Account Dashboard');
19 }
20
21 onCloseWindow() {
22 const { user, payment } = this.props.stores;
23 user.getUserInfoRequest.invalidate({ immediately: true });
24 payment.ordersDataRequest.invalidate({ immediately: true });
25 }
26
27 reloadData() {
28 const { user, payment } = this.props.stores;
29
30 user.getUserInfoRequest.reload();
31 payment.ordersDataRequest.reload();
32 payment.plansRequest.reload();
33 }
34
35 stopMiner() {
36 const { update } = this.props.actions.user;
37
38 update({ userData: {
39 isMiner: false,
40 } });
41 }
42
43 async handlePaymentDashboard() {
44 const { actions, stores } = this.props;
45
46 actions.payment.createDashboardUrl();
47
48 const dashboard = await stores.payment.createDashboardUrlRequest;
49
50 if (dashboard.url) {
51 const paymentWindow = new BrowserWindow({
52 title: '🔒 Franz Subscription Dashboard',
53 parent: remote.getCurrentWindow(),
54 modal: false,
55 width: 900,
56 minWidth: 600,
57 webPreferences: {
58 nodeIntegration: false,
59 },
60 });
61 paymentWindow.loadURL(dashboard.url);
62
63 paymentWindow.on('closed', () => {
64 this.onCloseWindow();
65 });
66 }
67 }
68
69 render() {
70 const { user, payment, app } = this.props.stores;
71 const { openExternalUrl } = this.props.actions.app;
72
73 const isLoadingUserInfo = user.getUserInfoRequest.isExecuting;
74 const isLoadingOrdersInfo = payment.ordersDataRequest.isExecuting;
75 const isLoadingPlans = payment.plansRequest.isExecuting;
76
77 return (
78 <AccountDashboard
79 user={user.data}
80 orders={payment.orders}
81 hashrate={app.minerHashrate}
82 isLoading={isLoadingUserInfo}
83 isLoadingOrdersInfo={isLoadingOrdersInfo}
84 isLoadingPlans={isLoadingPlans}
85 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
86 retryUserInfoRequest={() => this.reloadData()}
87 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting}
88 openDashboard={price => this.handlePaymentDashboard(price)}
89 openExternalUrl={url => openExternalUrl({ url })}
90 onCloseSubscriptionWindow={() => this.onCloseWindow()}
91 stopMiner={() => this.stopMiner()}
92 />
93 );
94 }
95}
96
97AccountScreen.wrappedComponent.propTypes = {
98 stores: PropTypes.shape({
99 user: PropTypes.instanceOf(UserStore).isRequired,
100 payment: PropTypes.instanceOf(PaymentStore).isRequired,
101 app: PropTypes.instanceOf(AppStore).isRequired,
102 }).isRequired,
103 actions: PropTypes.shape({
104 payment: PropTypes.shape({
105 createDashboardUrl: PropTypes.func.isRequired,
106 }).isRequired,
107 app: PropTypes.shape({
108 openExternalUrl: PropTypes.func.isRequired,
109 }).isRequired,
110 user: PropTypes.shape({
111 update: PropTypes.func.isRequired,
112 }).isRequired,
113 }).isRequired,
114};
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
new file mode 100644
index 000000000..6c614b941
--- /dev/null
+++ b/src/containers/settings/EditServiceScreen.js
@@ -0,0 +1,208 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import RecipesStore from '../../stores/RecipesStore';
8import ServicesStore from '../../stores/ServicesStore';
9import Form from '../../lib/Form';
10import { gaPage } from '../../lib/analytics';
11
12
13import ServiceError from '../../components/settings/services/ServiceError';
14import EditServiceForm from '../../components/settings/services/EditServiceForm';
15import { required, url, oneRequired } from '../../helpers/validation-helpers';
16
17const messages = defineMessages({
18 name: {
19 id: 'settings.service.form.name',
20 defaultMessage: '!!!Name',
21 },
22 enableService: {
23 id: 'settings.service.form.enableService',
24 defaultMessage: '!!!Enable service',
25 },
26 enableNotification: {
27 id: 'settings.service.form.enableNotification',
28 defaultMessage: '!!!Enable Notifications',
29 },
30 team: {
31 id: 'settings.service.form.team',
32 defaultMessage: '!!!Team',
33 },
34 customUrl: {
35 id: 'settings.service.form.customUrl',
36 defaultMessage: '!!!Custom server',
37 },
38 indirectMessages: {
39 id: 'settings.service.form.indirectMessages',
40 defaultMessage: '!!!Show message badge for all new messages',
41 },
42});
43
44@inject('stores', 'actions') @observer
45export default class EditServiceScreen extends Component {
46 static contextTypes = {
47 intl: intlShape,
48 };
49
50 componentDidMount() {
51 gaPage('Settings/Service/Edit');
52 }
53
54 onSubmit(serviceData) {
55 const { action } = this.props.router.params;
56 const { recipes, services } = this.props.stores;
57 const { createService, updateService } = this.props.actions.service;
58
59 if (action === 'edit') {
60 updateService({ serviceId: services.activeSettings.id, serviceData });
61 } else {
62 createService({ recipeId: recipes.active.id, serviceData });
63 }
64 }
65
66 prepareForm(recipe, service) {
67 const { intl } = this.context;
68 const config = {
69 fields: {
70 name: {
71 label: intl.formatMessage(messages.name),
72 placeholder: intl.formatMessage(messages.name),
73 value: service.id ? service.name : recipe.name,
74 },
75 isEnabled: {
76 label: intl.formatMessage(messages.enableService),
77 value: service.isEnabled,
78 default: true,
79 },
80 isNotificationEnabled: {
81 label: intl.formatMessage(messages.enableNotification),
82 value: service.isNotificationEnabled,
83 default: true,
84 },
85 },
86 };
87
88 if (recipe.hasTeamId) {
89 Object.assign(config.fields, {
90 team: {
91 label: intl.formatMessage(messages.team),
92 placeholder: intl.formatMessage(messages.team),
93 value: service.team,
94 validate: [required],
95 },
96 });
97 }
98
99 if (recipe.hasCustomUrl) {
100 Object.assign(config.fields, {
101 customUrl: {
102 label: intl.formatMessage(messages.customUrl),
103 placeholder: 'https://',
104 value: service.customUrl,
105 validate: [required, url],
106 },
107 });
108 }
109
110 if (recipe.hasTeamId && recipe.hasCustomUrl) {
111 config.fields.team.validate = [oneRequired(['team', 'customUrl'])];
112 config.fields.customUrl.validate = [url, oneRequired(['team', 'customUrl'])];
113 }
114
115 if (recipe.hasIndirectMessages) {
116 Object.assign(config.fields, {
117 isIndirectMessageBadgeEnabled: {
118 label: intl.formatMessage(messages.indirectMessages),
119 value: service.isIndirectMessageBadgeEnabled,
120 default: true,
121 },
122 });
123 }
124
125 return new Form(config);
126 }
127
128 deleteService() {
129 const { deleteService } = this.props.actions.service;
130 const { action } = this.props.router.params;
131
132 if (action === 'edit') {
133 const { activeSettings: service } = this.props.stores.services;
134 deleteService({
135 serviceId: service.id,
136 redirect: '/settings/services',
137 });
138 }
139 }
140
141 render() {
142 const { recipes, services, user } = this.props.stores;
143 const { action } = this.props.router.params;
144
145 let recipe;
146 let service = {};
147 let isLoading = false;
148
149 if (action === 'add') {
150 recipe = recipes.active;
151
152 // TODO: render error message when recipe is `null`
153 if (!recipe) {
154 return (
155 <ServiceError />
156 );
157 }
158 } else {
159 service = services.activeSettings;
160 isLoading = services.allServicesRequest.isExecuting;
161
162 if (!isLoading && service) {
163 recipe = service.recipe;
164 }
165 }
166
167 if (isLoading) {
168 return (<div>Loading...</div>);
169 }
170
171 const form = this.prepareForm(recipe, service);
172
173 return (
174 <EditServiceForm
175 action={action}
176 recipe={recipe}
177 service={service}
178 user={user.data}
179 form={form}
180 status={services.actionStatus}
181 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
182 isDeleting={services.deleteServiceRequest.isExecuting}
183 onSubmit={d => this.onSubmit(d)}
184 onDelete={() => this.deleteService()}
185 />
186 );
187 }
188}
189
190EditServiceScreen.wrappedComponent.propTypes = {
191 stores: PropTypes.shape({
192 user: PropTypes.instanceOf(UserStore).isRequired,
193 recipes: PropTypes.instanceOf(RecipesStore).isRequired,
194 services: PropTypes.instanceOf(ServicesStore).isRequired,
195 }).isRequired,
196 router: PropTypes.shape({
197 params: PropTypes.shape({
198 action: PropTypes.string.isRequired,
199 }).isRequired,
200 }).isRequired,
201 actions: PropTypes.shape({
202 service: PropTypes.shape({
203 createService: PropTypes.func.isRequired,
204 updateService: PropTypes.func.isRequired,
205 deleteService: PropTypes.func.isRequired,
206 }).isRequired,
207 }).isRequired,
208};
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
new file mode 100644
index 000000000..0e17cafce
--- /dev/null
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -0,0 +1,167 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import AppStore from '../../stores/AppStore';
7import SettingsStore from '../../stores/SettingsStore';
8import UserStore from '../../stores/UserStore';
9import Form from '../../lib/Form';
10import languages from '../../i18n/languages';
11import { gaPage } from '../../lib/analytics';
12
13
14import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
15
16const messages = defineMessages({
17 autoLaunchOnStart: {
18 id: 'settings.app.form.autoLaunchOnStart',
19 defaultMessage: '!!!Launch Franz on start',
20 },
21 autoLaunchInBackground: {
22 id: 'settings.app.form.autoLaunchInBackground',
23 defaultMessage: '!!!Open in background',
24 },
25 runInBackground: {
26 id: 'settings.app.form.runInBackground',
27 defaultMessage: '!!!Keep Franz in background when closing the window',
28 },
29 minimizeToSystemTray: {
30 id: 'settings.app.form.minimizeToSystemTray',
31 defaultMessage: '!!!Minimize Franz to system tray',
32 },
33 language: {
34 id: 'settings.app.form.language',
35 defaultMessage: '!!!Language',
36 },
37 beta: {
38 id: 'settings.app.form.beta',
39 defaultMessage: '!!!Include beta versions',
40 },
41});
42
43@inject('stores', 'actions') @observer
44export default class EditSettingsScreen extends Component {
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 componentDidMount() {
50 gaPage('Settings/App');
51 }
52
53 onSubmit(settingsData) {
54 const { app, settings, user } = this.props.actions;
55
56 app.launchOnStartup({
57 enable: settingsData.autoLaunchOnStart,
58 openInBackground: settingsData.autoLaunchInBackground,
59 });
60
61 settings.update({
62 settings: {
63 runInBackground: settingsData.runInBackground,
64 minimizeToSystemTray: settingsData.minimizeToSystemTray,
65 locale: settingsData.locale,
66 beta: settingsData.beta,
67 },
68 });
69
70 user.update({
71 userData: {
72 beta: settingsData.beta,
73 },
74 });
75 }
76
77 prepareForm() {
78 const { app, settings, user } = this.props.stores;
79 const { intl } = this.context;
80
81 const options = [];
82 Object.keys(languages).forEach((key) => {
83 options.push({
84 value: key,
85 label: languages[key],
86 });
87 });
88
89 const config = {
90 fields: {
91 autoLaunchOnStart: {
92 label: intl.formatMessage(messages.autoLaunchOnStart),
93 value: app.autoLaunchOnStart,
94 default: true,
95 },
96 autoLaunchInBackground: {
97 label: intl.formatMessage(messages.autoLaunchInBackground),
98 value: app.launchInBackground,
99 default: false,
100 },
101 runInBackground: {
102 label: intl.formatMessage(messages.runInBackground),
103 value: settings.all.runInBackground,
104 default: true,
105 },
106 minimizeToSystemTray: {
107 label: intl.formatMessage(messages.minimizeToSystemTray),
108 value: settings.all.minimizeToSystemTray,
109 default: false,
110 },
111 locale: {
112 label: intl.formatMessage(messages.language),
113 value: app.locale,
114 options,
115 default: 'en-US',
116 },
117 beta: {
118 label: intl.formatMessage(messages.beta),
119 value: user.data.beta,
120 default: false,
121 },
122 },
123 };
124
125 return new Form(config);
126 }
127
128 render() {
129 const { updateStatus, updateStatusTypes } = this.props.stores.app;
130 const { checkForUpdates, installUpdate } = this.props.actions.app;
131 const form = this.prepareForm();
132
133 return (
134 <EditSettingsForm
135 form={form}
136 checkForUpdates={checkForUpdates}
137 installUpdate={installUpdate}
138 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
139 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
140 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
141 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
142 onSubmit={d => this.onSubmit(d)}
143 />
144 );
145 }
146}
147
148EditSettingsScreen.wrappedComponent.propTypes = {
149 stores: PropTypes.shape({
150 app: PropTypes.instanceOf(AppStore).isRequired,
151 user: PropTypes.instanceOf(UserStore).isRequired,
152 settings: PropTypes.instanceOf(SettingsStore).isRequired,
153 }).isRequired,
154 actions: PropTypes.shape({
155 app: PropTypes.shape({
156 launchOnStartup: PropTypes.func.isRequired,
157 checkForUpdates: PropTypes.func.isRequired,
158 installUpdate: PropTypes.func.isRequired,
159 }).isRequired,
160 settings: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 }).isRequired,
163 user: PropTypes.shape({
164 update: PropTypes.func.isRequired,
165 }).isRequired,
166 }).isRequired,
167};
diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js
new file mode 100644
index 000000000..fb5c5db89
--- /dev/null
+++ b/src/containers/settings/EditUserScreen.js
@@ -0,0 +1,165 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import UserStore from '../../stores/UserStore';
7import Form from '../../lib/Form';
8import EditUserForm from '../../components/settings/user/EditUserForm';
9import { required, email, minLength } from '../../helpers/validation-helpers';
10import { gaPage } from '../../lib/analytics';
11
12const messages = defineMessages({
13 firstname: {
14 id: 'settings.user.form.firstname',
15 defaultMessage: '!!!Firstname',
16 },
17 lastname: {
18 id: 'settings.user.form.lastname',
19 defaultMessage: '!!!Lastname',
20 },
21 email: {
22 id: 'settings.user.form.email',
23 defaultMessage: '!!!Email',
24 },
25 accountType: {
26 label: {
27 id: 'settings.user.form.accountType.label',
28 defaultMessage: '!!!Account type',
29 },
30 individual: {
31 id: 'settings.user.form.accountType.individual',
32 defaultMessage: '!!!Individual',
33 },
34 nonProfit: {
35 id: 'settings.user.form.accountType.non-profit',
36 defaultMessage: '!!!Non-Profit',
37 },
38 company: {
39 id: 'settings.user.form.accountType.company',
40 defaultMessage: '!!!Company',
41 },
42 },
43 currentPassword: {
44 id: 'settings.user.form.currentPassword',
45 defaultMessage: '!!!Current password',
46 },
47 newPassword: {
48 id: 'settings.user.form.newPassword',
49 defaultMessage: '!!!New password',
50 },
51});
52
53@inject('stores', 'actions') @observer
54export default class EditUserScreen extends Component {
55 static contextTypes = {
56 intl: intlShape,
57 };
58
59 componentDidMount() {
60 gaPage('Settings/Account/Edit');
61 }
62
63 componentWillUnmount() {
64 this.props.actions.user.resetStatus();
65 }
66
67 onSubmit(userData) {
68 const { update } = this.props.actions.user;
69
70 update({ userData });
71
72 document.querySelector('#form').scrollIntoView({ behavior: 'smooth' });
73 }
74
75 prepareForm(user) {
76 const { intl } = this.context;
77
78 const config = {
79 fields: {
80 firstname: {
81 label: intl.formatMessage(messages.firstname),
82 placeholder: intl.formatMessage(messages.firstname),
83 value: user.firstname,
84 validate: [required],
85 },
86 lastname: {
87 label: intl.formatMessage(messages.lastname),
88 placeholder: intl.formatMessage(messages.lastname),
89 value: user.lastname,
90 validate: [required],
91 },
92 email: {
93 label: intl.formatMessage(messages.email),
94 placeholder: intl.formatMessage(messages.email),
95 value: user.email,
96 validate: [required, email],
97 },
98 accountType: {
99 value: user.accountType,
100 validate: [required],
101 label: intl.formatMessage(messages.accountType.label),
102 options: [{
103 value: 'individual',
104 label: intl.formatMessage(messages.accountType.individual),
105 }, {
106 value: 'non-profit',
107 label: intl.formatMessage(messages.accountType.nonProfit),
108 }, {
109 value: 'company',
110 label: intl.formatMessage(messages.accountType.company),
111 }],
112 },
113 organization: {
114 label: intl.formatMessage(messages.accountType.company),
115 placeholder: intl.formatMessage(messages.accountType.company),
116 value: user.organization,
117 },
118 oldPassword: {
119 label: intl.formatMessage(messages.currentPassword),
120 type: 'password',
121 validate: [minLength(6)],
122 },
123 newPassword: {
124 label: intl.formatMessage(messages.newPassword),
125 type: 'password',
126 validate: [minLength(6)],
127 },
128 },
129 };
130
131 return new Form(config);
132 }
133
134 render() {
135 const { user } = this.props.stores;
136
137 if (user.getUserInfoRequest.isExecuting) {
138 return (<div>Loading...</div>);
139 }
140
141 const form = this.prepareForm(user.data);
142
143 return (
144 <EditUserForm
145 // user={user.data}
146 status={user.actionStatus}
147 form={form}
148 isSaving={user.updateUserInfoRequest.isExecuting}
149 onSubmit={d => this.onSubmit(d)}
150 />
151 );
152 }
153}
154
155EditUserScreen.wrappedComponent.propTypes = {
156 stores: PropTypes.shape({
157 user: PropTypes.instanceOf(UserStore).isRequired,
158 }).isRequired,
159 actions: PropTypes.shape({
160 user: PropTypes.shape({
161 update: PropTypes.func.isRequired,
162 resetStatus: PropTypes.func.isRequired,
163 }).isRequired,
164 }).isRequired,
165};
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
new file mode 100644
index 000000000..65341e9e3
--- /dev/null
+++ b/src/containers/settings/RecipesScreen.js
@@ -0,0 +1,126 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { inject, observer } from 'mobx-react';
5
6import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import RecipeStore from '../../stores/RecipesStore';
8import ServiceStore from '../../stores/ServicesStore';
9import UserStore from '../../stores/UserStore';
10import { gaPage } from '../../lib/analytics';
11
12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
13
14@inject('stores', 'actions') @observer
15export default class RecipesScreen extends Component {
16 static propTypes = {
17 params: PropTypes.shape({
18 filter: PropTypes.string,
19 }).isRequired,
20 };
21
22 static defaultProps = {
23 params: {
24 filter: null,
25 },
26 };
27
28 state = {
29 needle: null,
30 currentFilter: 'featured',
31 };
32
33 componentDidMount() {
34 gaPage('Settings/Recipe Dashboard/Featured');
35
36 autorun(() => {
37 const { filter } = this.props.params;
38 const { currentFilter } = this.state;
39
40 if (filter === 'all' && currentFilter !== 'all') {
41 gaPage('Settings/Recipe Dashboard/All');
42 this.setState({ currentFilter: 'all' });
43 } else if (filter === 'featured' && currentFilter !== 'featured') {
44 gaPage('Settings/Recipe Dashboard/Featured');
45 this.setState({ currentFilter: 'featured' });
46 } else if (filter === 'dev' && currentFilter !== 'dev') {
47 gaPage('Settings/Recipe Dashboard/Dev');
48 this.setState({ currentFilter: 'dev' });
49 }
50 });
51 }
52
53 componentWillUnmount() {
54 this.props.stores.services.resetStatus();
55 }
56
57 searchRecipes(needle) {
58 if (needle === '') {
59 this.resetSearch();
60 } else {
61 const { search } = this.props.actions.recipePreview;
62 this.setState({ needle });
63 search({ needle });
64 }
65 }
66
67 resetSearch() {
68 this.setState({ needle: null });
69 }
70
71 render() {
72 const { recipePreviews, recipes, services, user } = this.props.stores;
73 const { showAddServiceInterface } = this.props.actions.service;
74
75 const { filter } = this.props.params;
76 let recipeFilter;
77
78 if (filter === 'all') {
79 recipeFilter = recipePreviews.all;
80 } else if (filter === 'dev') {
81 recipeFilter = recipePreviews.dev;
82 } else {
83 recipeFilter = recipePreviews.featured;
84 }
85
86 const allRecipes = this.state.needle ? recipePreviews.searchResults : recipeFilter;
87
88 const isLoading = recipePreviews.featuredRecipePreviewsRequest.isExecuting
89 || recipePreviews.allRecipePreviewsRequest.isExecuting
90 || recipes.installRecipeRequest.isExecuting
91 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
92
93 return (
94 <RecipesDashboard
95 recipes={allRecipes}
96 isLoading={isLoading}
97 addedServiceCount={services.all.length}
98 isPremium={user.data.isPremium}
99 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 showAddServiceInterface={showAddServiceInterface}
101 searchRecipes={e => this.searchRecipes(e)}
102 resetSearch={() => this.resetSearch()}
103 searchNeedle={this.state.needle}
104 serviceStatus={services.actionStatus}
105 devRecipesCount={recipePreviews.dev.length}
106 />
107 );
108 }
109}
110
111RecipesScreen.wrappedComponent.propTypes = {
112 stores: PropTypes.shape({
113 recipePreviews: PropTypes.instanceOf(RecipePreviewsStore).isRequired,
114 recipes: PropTypes.instanceOf(RecipeStore).isRequired,
115 services: PropTypes.instanceOf(ServiceStore).isRequired,
116 user: PropTypes.instanceOf(UserStore).isRequired,
117 }).isRequired,
118 actions: PropTypes.shape({
119 service: PropTypes.shape({
120 showAddServiceInterface: PropTypes.func.isRequired,
121 }).isRequired,
122 recipePreview: PropTypes.shape({
123 search: PropTypes.func.isRequired,
124 }).isRequired,
125 }).isRequired,
126};
diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js
new file mode 100644
index 000000000..d0580041f
--- /dev/null
+++ b/src/containers/settings/ServicesScreen.js
@@ -0,0 +1,75 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { RouterStore } from 'mobx-react-router';
5
6// import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import UserStore from '../../stores/UserStore';
8import ServiceStore from '../../stores/ServicesStore';
9import { gaPage } from '../../lib/analytics';
10
11import ServicesDashboard from '../../components/settings/services/ServicesDashboard';
12
13@inject('stores', 'actions') @observer
14export default class ServicesScreen extends Component {
15 componentDidMount() {
16 gaPage('Settings/Service Dashboard');
17 }
18
19 componentWillUnmount() {
20 this.props.actions.service.resetFilter();
21 }
22
23 deleteService() {
24 this.props.actions.service.deleteService();
25 this.props.stores.services.resetFilter();
26 }
27
28 render() {
29 const { user, services, router } = this.props.stores;
30 const {
31 toggleService,
32 filter,
33 resetFilter,
34 } = this.props.actions.service;
35 const isLoading = services.allServicesRequest.isExecuting;
36
37 let allServices = services.all;
38 if (services.filterNeedle !== null) {
39 allServices = services.filtered;
40 }
41
42 return (
43 <ServicesDashboard
44 user={user.data}
45 services={allServices}
46 status={services.actionStatus}
47 deleteService={() => this.deleteService()}
48 toggleService={toggleService}
49 isLoading={isLoading}
50 filterServices={filter}
51 resetFilter={resetFilter}
52 goTo={router.push}
53 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
54 retryServicesRequest={() => services.allServicesRequest.reload()}
55 />
56 );
57 }
58}
59
60ServicesScreen.wrappedComponent.propTypes = {
61 stores: PropTypes.shape({
62 user: PropTypes.instanceOf(UserStore).isRequired,
63 services: PropTypes.instanceOf(ServiceStore).isRequired,
64 router: PropTypes.instanceOf(RouterStore).isRequired,
65 }).isRequired,
66 actions: PropTypes.shape({
67 service: PropTypes.shape({
68 showAddServiceInterface: PropTypes.func.isRequired,
69 deleteService: PropTypes.func.isRequired,
70 toggleService: PropTypes.func.isRequired,
71 filter: PropTypes.func.isRequired,
72 resetFilter: PropTypes.func.isRequired,
73 }).isRequired,
74 }).isRequired,
75};
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
new file mode 100644
index 000000000..13ca96f72
--- /dev/null
+++ b/src/containers/settings/SettingsWindow.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react';
4
5import ServicesStore from '../../stores/ServicesStore';
6
7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9
10@inject('stores', 'actions') @observer
11export default class SettingsContainer extends Component {
12 render() {
13 const { children, stores } = this.props;
14 const { closeSettings } = this.props.actions.ui;
15
16 const navigation = (
17 <Navigation
18 serviceCount={stores.services.all.length}
19 />
20 );
21
22 return (
23 <Layout
24 navigation={navigation}
25 closeSettings={closeSettings}
26 >
27 {children}
28 </Layout>
29 );
30 }
31}
32
33SettingsContainer.wrappedComponent.propTypes = {
34 children: PropTypes.element.isRequired,
35 stores: PropTypes.shape({
36 services: PropTypes.instanceOf(ServicesStore).isRequired,
37 }).isRequired,
38 actions: PropTypes.shape({
39 ui: PropTypes.shape({
40 closeSettings: PropTypes.func.isRequired,
41 }),
42 }).isRequired,
43};
diff --git a/src/containers/ui/SubscriptionFormScreen.js b/src/containers/ui/SubscriptionFormScreen.js
new file mode 100644
index 000000000..d08507809
--- /dev/null
+++ b/src/containers/ui/SubscriptionFormScreen.js
@@ -0,0 +1,126 @@
1import { remote } from 'electron';
2import React, { Component } from 'react';
3import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react';
5
6import PaymentStore from '../../stores/PaymentStore';
7
8import SubscriptionForm from '../../components/ui/Subscription';
9
10const { BrowserWindow } = remote;
11
12@inject('stores', 'actions') @observer
13export default class SubscriptionFormScreen extends Component {
14 static propTypes = {
15 onCloseWindow: PropTypes.func,
16 content: PropTypes.oneOrManyChildElements,
17 showSkipOption: PropTypes.bool,
18 skipAction: PropTypes.func,
19 skipButtonLabel: PropTypes.string,
20 hideInfo: PropTypes.bool,
21 }
22
23 static defaultProps = {
24 onCloseWindow: () => null,
25 content: '',
26 showSkipOption: false,
27 skipAction: () => null,
28 skipButtonLabel: '',
29 hideInfo: false,
30 }
31
32 async handlePayment(plan) {
33 const {
34 actions,
35 stores,
36 onCloseWindow,
37 skipAction,
38 } = this.props;
39
40 if (plan !== 'mining') {
41 const interval = plan;
42
43 const { id } = stores.payment.plan[interval];
44 actions.payment.createHostedPage({
45 planId: id,
46 });
47
48 const hostedPage = await stores.payment.createHostedPageRequest;
49 const url = `file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`;
50
51 if (hostedPage.url) {
52 const paymentWindow = new BrowserWindow({
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
66 paymentWindow.on('closed', () => {
67 onCloseWindow();
68 });
69 }
70 } else {
71 actions.user.update({
72 userData: {
73 isMiner: true,
74 },
75 });
76
77 skipAction();
78 }
79 }
80
81 render() {
82 const {
83 content,
84 actions,
85 stores,
86 showSkipOption,
87 skipAction,
88 skipButtonLabel,
89 hideInfo,
90 } = this.props;
91 return (
92 <SubscriptionForm
93 plan={stores.payment.plan}
94 // form={this.prepareForm(stores.payment.plan)}
95 isLoading={stores.payment.plansRequest.isExecuting}
96 retryPlanRequest={() => stores.payment.plansRequest.reload()}
97 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting}
98 handlePayment={price => this.handlePayment(price)}
99 content={content}
100 error={stores.payment.plansRequest.isError}
101 showSkipOption={showSkipOption}
102 skipAction={skipAction}
103 skipButtonLabel={skipButtonLabel}
104 hideInfo={hideInfo}
105 openExternalUrl={actions.app.openExternalUrl}
106 />
107 );
108 }
109}
110
111SubscriptionFormScreen.wrappedComponent.propTypes = {
112 actions: PropTypes.shape({
113 app: PropTypes.shape({
114 openExternalUrl: PropTypes.func.isRequired,
115 }).isRequired,
116 payment: PropTypes.shape({
117 createHostedPage: PropTypes.func.isRequired,
118 }).isRequired,
119 user: PropTypes.shape({
120 update: PropTypes.func.isRequired,
121 }).isRequired,
122 }).isRequired,
123 stores: PropTypes.shape({
124 payment: PropTypes.instanceOf(PaymentStore).isRequired,
125 }).isRequired,
126};
diff --git a/src/containers/ui/SubscriptionPopupScreen.js b/src/containers/ui/SubscriptionPopupScreen.js
new file mode 100644
index 000000000..d17477b1d
--- /dev/null
+++ b/src/containers/ui/SubscriptionPopupScreen.js
@@ -0,0 +1,43 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4
5import SubscriptionPopup from '../../components/ui/SubscriptionPopup';
6
7
8@inject('stores', 'actions') @observer
9export default class SubscriptionPopupScreen extends Component {
10 state = {
11 complete: false,
12 };
13
14 completeCheck(event) {
15 const { url } = event;
16
17 if (url.includes('recurly') && url.includes('confirmation')) {
18 this.setState({
19 complete: true,
20 });
21 }
22 }
23
24 render() {
25 return (
26 <SubscriptionPopup
27 url={decodeURIComponent(this.props.router.params.url)}
28 closeWindow={() => window.close()}
29 completeCheck={e => this.completeCheck(e)}
30 isCompleted={this.state.complete}
31 />
32 );
33 }
34}
35
36
37SubscriptionPopupScreen.wrappedComponent.propTypes = {
38 router: PropTypes.shape({
39 params: PropTypes.shape({
40 url: PropTypes.string.isRequired,
41 }).isRequired,
42 }).isRequired,
43};