diff options
Diffstat (limited to 'src/components')
47 files changed, 736 insertions, 296 deletions
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js index 4fb0e6a59..ac8fdbe5b 100644 --- a/src/components/auth/AuthLayout.js +++ b/src/components/auth/AuthLayout.js | |||
@@ -1,7 +1,6 @@ | |||
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 } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { RouteTransition } from 'react-router-transition'; | ||
5 | import { intlShape } from 'react-intl'; | 4 | import { intlShape } from 'react-intl'; |
6 | import { TitleBar } from 'electron-react-titlebar'; | 5 | import { TitleBar } from 'electron-react-titlebar'; |
7 | 6 | ||
@@ -16,7 +15,6 @@ import { isWindows } from '../../environment'; | |||
16 | export default @observer class AuthLayout extends Component { | 15 | export default @observer class AuthLayout extends Component { |
17 | static propTypes = { | 16 | static propTypes = { |
18 | children: oneOrManyChildElements.isRequired, | 17 | children: oneOrManyChildElements.isRequired, |
19 | pathname: PropTypes.string.isRequired, | ||
20 | error: globalErrorPropType.isRequired, | 18 | error: globalErrorPropType.isRequired, |
21 | isOnline: PropTypes.bool.isRequired, | 19 | isOnline: PropTypes.bool.isRequired, |
22 | isAPIHealthy: PropTypes.bool.isRequired, | 20 | isAPIHealthy: PropTypes.bool.isRequired, |
@@ -33,7 +31,6 @@ export default @observer class AuthLayout extends Component { | |||
33 | render() { | 31 | render() { |
34 | const { | 32 | const { |
35 | children, | 33 | children, |
36 | pathname, | ||
37 | error, | 34 | error, |
38 | isOnline, | 35 | isOnline, |
39 | isAPIHealthy, | 36 | isAPIHealthy, |
@@ -46,8 +43,8 @@ export default @observer class AuthLayout extends Component { | |||
46 | 43 | ||
47 | return ( | 44 | return ( |
48 | <div className={darkMode ? 'theme__dark' : ''}> | 45 | <div className={darkMode ? 'theme__dark' : ''}> |
49 | {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} | 46 | {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} |
50 | <div className={'auth'}> | 47 | <div className="auth"> |
51 | {!isOnline && ( | 48 | {!isOnline && ( |
52 | <InfoBar | 49 | <InfoBar |
53 | type="warning" | 50 | type="warning" |
@@ -69,22 +66,10 @@ export default @observer class AuthLayout extends Component { | |||
69 | </InfoBar> | 66 | </InfoBar> |
70 | )} | 67 | )} |
71 | <div className="auth__layout"> | 68 | <div className="auth__layout"> |
72 | <RouteTransition | 69 | {/* Inject globalError into children */} |
73 | pathname={pathname} | 70 | {React.cloneElement(children, { |
74 | atEnter={{ opacity: 0 }} | 71 | error, |
75 | atLeave={{ opacity: 0 }} | 72 | })} |
76 | atActive={{ opacity: 1 }} | ||
77 | mapStyles={styles => ({ | ||
78 | transform: `translateX(${styles.translateX}%)`, | ||
79 | opacity: styles.opacity, | ||
80 | })} | ||
81 | component="span" | ||
82 | > | ||
83 | {/* Inject globalError into children */} | ||
84 | {React.cloneElement(children, { | ||
85 | error, | ||
86 | })} | ||
87 | </RouteTransition> | ||
88 | </div> | 73 | </div> |
89 | {/* </div> */} | 74 | {/* </div> */} |
90 | <Link to="https://adlk.io" className="auth__adlk" target="_blank"> | 75 | <Link to="https://adlk.io" className="auth__adlk" target="_blank"> |
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js index 96821a61a..fd957ee73 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | 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'; |
@@ -127,7 +127,7 @@ export default @observer class Invite extends Component { | |||
127 | }); | 127 | }); |
128 | 128 | ||
129 | const renderForm = ( | 129 | const renderForm = ( |
130 | <div> | 130 | <Fragment> |
131 | {this.state.showSuccessInfo && isInviteSuccessful && ( | 131 | {this.state.showSuccessInfo && isInviteSuccessful && ( |
132 | <Appear> | 132 | <Appear> |
133 | <Infobox | 133 | <Infobox |
@@ -141,11 +141,13 @@ export default @observer class Invite extends Component { | |||
141 | )} | 141 | )} |
142 | 142 | ||
143 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 143 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> |
144 | {!embed && (<img | 144 | {!embed && ( |
145 | src="./assets/images/logo.svg" | 145 | <img |
146 | className="auth__logo" | 146 | src="./assets/images/logo.svg" |
147 | alt="" | 147 | className="auth__logo" |
148 | />)} | 148 | alt="" |
149 | /> | ||
150 | )} | ||
149 | <h1 className={embed && 'invite__embed'}> | 151 | <h1 className={embed && 'invite__embed'}> |
150 | {intl.formatMessage(messages.headline)} | 152 | {intl.formatMessage(messages.headline)} |
151 | </h1> | 153 | </h1> |
@@ -164,14 +166,16 @@ export default @observer class Invite extends Component { | |||
164 | label={intl.formatMessage(messages.submitButtonLabel)} | 166 | label={intl.formatMessage(messages.submitButtonLabel)} |
165 | loaded={!isLoadingInvite} | 167 | loaded={!isLoadingInvite} |
166 | /> | 168 | /> |
167 | {!embed && (<Link | 169 | {!embed && ( |
168 | to="/" | 170 | <Link |
169 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | 171 | to="/" |
170 | > | 172 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" |
171 | {intl.formatMessage(messages.skipButtonLabel)} | 173 | > |
172 | </Link>)} | 174 | {intl.formatMessage(messages.skipButtonLabel)} |
175 | </Link> | ||
176 | )} | ||
173 | </form> | 177 | </form> |
174 | </div> | 178 | </Fragment> |
175 | ); | 179 | ); |
176 | 180 | ||
177 | return ( | 181 | return ( |
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js index f465b35a5..5d21f8b60 100644 --- a/src/components/auth/Login.js +++ b/src/components/auth/Login.js | |||
@@ -11,11 +11,8 @@ import Button from '../ui/Button'; | |||
11 | import Link from '../ui/Link'; | 11 | import Link from '../ui/Link'; |
12 | import Infobox from '../ui/Infobox'; | 12 | import Infobox from '../ui/Infobox'; |
13 | 13 | ||
14 | |||
15 | import { globalError as globalErrorPropType } from '../../prop-types'; | 14 | import { globalError as globalErrorPropType } from '../../prop-types'; |
16 | 15 | ||
17 | // import Appear from '../ui/effects/Appear'; | ||
18 | |||
19 | const messages = defineMessages({ | 16 | const messages = defineMessages({ |
20 | headline: { | 17 | headline: { |
21 | id: 'login.headline', | 18 | id: 'login.headline', |
@@ -86,18 +83,18 @@ export default @observer class Login extends Component { | |||
86 | }, | 83 | }, |
87 | }, this.context.intl); | 84 | }, this.context.intl); |
88 | 85 | ||
86 | emailField = null; | ||
87 | |||
89 | submit(e) { | 88 | submit(e) { |
90 | e.preventDefault(); | 89 | e.preventDefault(); |
91 | this.form.submit({ | 90 | this.form.submit({ |
92 | onSuccess: (form) => { | 91 | onSuccess: (form) => { |
93 | this.props.onSubmit(form.values()); | 92 | this.props.onSubmit(form.values()); |
94 | }, | 93 | }, |
95 | onError: () => {}, | 94 | onError: () => { }, |
96 | }); | 95 | }); |
97 | } | 96 | } |
98 | 97 | ||
99 | emailField = null; | ||
100 | |||
101 | render() { | 98 | render() { |
102 | const { form } = this; | 99 | const { form } = this; |
103 | const { intl } = this.context; | 100 | const { intl } = this.context; |
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index f08129568..7ab14f429 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js | |||
@@ -69,18 +69,29 @@ export default @observer class Signup extends Component { | |||
69 | donor.amount ? ( | 69 | donor.amount ? ( |
70 | <span> | 70 | <span> |
71 | <p> | 71 | <p> |
72 | Thank you so much for your previous donation of <strong>$ {donor.amount}</strong>. | 72 | Thank you so much for your previous donation of |
73 | {' '} | ||
74 | <strong> | ||
75 | $ | ||
76 | {donor.amount} | ||
77 | </strong> | ||
78 | . | ||
73 | <br /> | 79 | <br /> |
74 | Your support allowed us to get where we are today. | 80 | Your support allowed us to get where we are today. |
75 | <br /> | 81 | <br /> |
76 | </p> | 82 | </p> |
77 | <p> | 83 | <p> |
78 | As an early supporter, you get <strong>a lifetime premium supporter license</strong> without any | 84 | As an early supporter, you get |
85 | {' '} | ||
86 | <strong>a lifetime premium supporter license</strong> | ||
87 | {' '} | ||
88 | without any | ||
79 | additional charges. | 89 | additional charges. |
80 | </p> | 90 | </p> |
81 | <p> | 91 | <p> |
82 | However, If you want to keep supporting us, you are more than welcome to subscribe to a plan. | 92 | However, If you want to keep supporting us, you are more than welcome to subscribe to a plan. |
83 | <br /><br /> | 93 | <br /> |
94 | <br /> | ||
84 | </p> | 95 | </p> |
85 | </span> | 96 | </span> |
86 | ) : ( | 97 | ) : ( |
@@ -113,12 +124,6 @@ export default @observer class Signup extends Component { | |||
113 | hideInfo={Boolean(donor.amount)} | 124 | hideInfo={Boolean(donor.amount)} |
114 | skipButtonLabel={intl.formatMessage(messages.skipPayment)} | 125 | skipButtonLabel={intl.formatMessage(messages.skipPayment)} |
115 | /> | 126 | /> |
116 | {/* <Link | ||
117 | to={inviteRoute} | ||
118 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | ||
119 | > | ||
120 | {intl.formatMessage(messages.skipPayment)} | ||
121 | </Link> */} | ||
122 | </Appear> | 127 | </Appear> |
123 | </Loader> | 128 | </Loader> |
124 | </form> | 129 | </form> |
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js index bbcad8b67..d9b83eeb8 100644 --- a/src/components/auth/Signup.js +++ b/src/components/auth/Signup.js | |||
@@ -199,7 +199,8 @@ export default @observer class Signup extends Component { | |||
199 | className="link" | 199 | className="link" |
200 | > | 200 | > |
201 | {intl.formatMessage(messages.privacy)} | 201 | {intl.formatMessage(messages.privacy)} |
202 | </Link>. | 202 | </Link> |
203 | . | ||
203 | </p> | 204 | </p> |
204 | </form> | 205 | </form> |
205 | <div className="auth__links"> | 206 | <div className="auth__links"> |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 3ababe54a..dbe0bb4b6 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -6,6 +6,8 @@ import { TitleBar } from 'electron-react-titlebar'; | |||
6 | 6 | ||
7 | import InfoBar from '../ui/InfoBar'; | 7 | import InfoBar from '../ui/InfoBar'; |
8 | import { Component as DelayApp } from '../../features/delayApp'; | 8 | import { Component as DelayApp } from '../../features/delayApp'; |
9 | import ErrorBoundary from '../util/ErrorBoundary'; | ||
10 | |||
9 | import globalMessages from '../../i18n/globalMessages'; | 11 | import globalMessages from '../../i18n/globalMessages'; |
10 | 12 | ||
11 | import { isWindows } from '../../environment'; | 13 | import { isWindows } from '../../environment'; |
@@ -94,74 +96,78 @@ export default @observer class AppLayout extends Component { | |||
94 | const { intl } = this.context; | 96 | const { intl } = this.context; |
95 | 97 | ||
96 | return ( | 98 | return ( |
97 | <div className={(darkMode ? 'theme__dark' : '')}> | 99 | <ErrorBoundary> |
98 | <div className="app"> | 100 | <div className={(darkMode ? 'theme__dark' : '')}> |
99 | {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} | 101 | <div className="app"> |
100 | <div className="app__content"> | 102 | {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} |
101 | {sidebar} | 103 | <div className="app__content"> |
102 | <div className="app__service"> | 104 | {sidebar} |
103 | {news.length > 0 && news.map(item => ( | 105 | <div className="app__service"> |
104 | <InfoBar | 106 | {news.length > 0 && news.map(item => ( |
105 | key={item.id} | 107 | <InfoBar |
106 | position="top" | 108 | key={item.id} |
107 | type={item.type} | 109 | position="top" |
108 | sticky={item.sticky} | 110 | type={item.type} |
109 | onHide={() => removeNewsItem({ newsId: item.id })} | 111 | sticky={item.sticky} |
110 | > | 112 | onHide={() => removeNewsItem({ newsId: item.id })} |
111 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> | 113 | > |
112 | </InfoBar> | 114 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> |
113 | ))} | 115 | </InfoBar> |
114 | {!isOnline && ( | 116 | ))} |
115 | <InfoBar | 117 | {!isOnline && ( |
116 | type="danger" | 118 | <InfoBar |
117 | > | 119 | type="danger" |
118 | <span className="mdi mdi-flash" /> | 120 | > |
119 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} | 121 | <span className="mdi mdi-flash" /> |
120 | </InfoBar> | 122 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} |
121 | )} | 123 | </InfoBar> |
122 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( | 124 | )} |
123 | <InfoBar | 125 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( |
124 | type="danger" | 126 | <InfoBar |
125 | ctaLabel="Try again" | 127 | type="danger" |
126 | ctaLoading={areRequiredRequestsLoading} | 128 | ctaLabel="Try again" |
127 | sticky | 129 | ctaLoading={areRequiredRequestsLoading} |
128 | onClick={retryRequiredRequests} | 130 | sticky |
129 | > | 131 | onClick={retryRequiredRequests} |
130 | <span className="mdi mdi-flash" /> | 132 | > |
131 | {intl.formatMessage(messages.requiredRequestsFailed)} | 133 | <span className="mdi mdi-flash" /> |
132 | </InfoBar> | 134 | {intl.formatMessage(messages.requiredRequestsFailed)} |
133 | )} | 135 | </InfoBar> |
134 | {showServicesUpdatedInfoBar && ( | 136 | )} |
135 | <InfoBar | 137 | {showServicesUpdatedInfoBar && ( |
136 | type="primary" | 138 | <InfoBar |
137 | ctaLabel={intl.formatMessage(messages.buttonReloadServices)} | 139 | type="primary" |
138 | onClick={reloadServicesAfterUpdate} | 140 | ctaLabel={intl.formatMessage(messages.buttonReloadServices)} |
139 | sticky | 141 | onClick={reloadServicesAfterUpdate} |
140 | > | 142 | sticky |
141 | <span className="mdi mdi-power-plug" /> | 143 | > |
142 | {intl.formatMessage(messages.servicesUpdated)} | 144 | <span className="mdi mdi-power-plug" /> |
143 | </InfoBar> | 145 | {intl.formatMessage(messages.servicesUpdated)} |
144 | )} | 146 | </InfoBar> |
145 | {appUpdateIsDownloaded && ( | 147 | )} |
146 | <InfoBar | 148 | {appUpdateIsDownloaded && ( |
147 | type="primary" | 149 | <InfoBar |
148 | ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} | 150 | type="primary" |
149 | onClick={installAppUpdate} | 151 | ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} |
150 | sticky | 152 | onClick={installAppUpdate} |
151 | > | 153 | sticky |
152 | <span className="mdi mdi-information" /> | 154 | > |
153 | {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank"> | 155 | <span className="mdi mdi-information" /> |
154 | <u>{intl.formatMessage(messages.changelog)}</u> | 156 | {intl.formatMessage(messages.updateAvailable)} |
155 | </a> | 157 | {' '} |
156 | </InfoBar> | 158 | <a href="https://meetfranz.com/changelog" target="_blank"> |
157 | )} | 159 | <u>{intl.formatMessage(messages.changelog)}</u> |
158 | {isDelayAppScreenVisible && (<DelayApp />)} | 160 | </a> |
159 | {services} | 161 | </InfoBar> |
162 | )} | ||
163 | {isDelayAppScreenVisible && (<DelayApp />)} | ||
164 | {services} | ||
165 | </div> | ||
160 | </div> | 166 | </div> |
161 | </div> | 167 | </div> |
168 | {children} | ||
162 | </div> | 169 | </div> |
163 | {children} | 170 | </ErrorBoundary> |
164 | </div> | ||
165 | ); | 171 | ); |
166 | } | 172 | } |
167 | } | 173 | } |
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 6ea95bf88..609a3b604 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js | |||
@@ -65,6 +65,7 @@ export default @observer class Sidebar extends Component { | |||
65 | disableToolTip={() => this.disableToolTip()} | 65 | disableToolTip={() => this.disableToolTip()} |
66 | /> | 66 | /> |
67 | <button | 67 | <button |
68 | type="button" | ||
68 | onClick={toggleMuteApp} | 69 | onClick={toggleMuteApp} |
69 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} | 70 | className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} |
70 | data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} | 71 | data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} |
@@ -72,6 +73,7 @@ export default @observer class Sidebar extends Component { | |||
72 | <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> | 73 | <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> |
73 | </button> | 74 | </button> |
74 | <button | 75 | <button |
76 | type="button" | ||
75 | onClick={() => openSettings({ path: 'recipes' })} | 77 | onClick={() => openSettings({ path: 'recipes' })} |
76 | className="sidebar__button sidebar__button--new-service" | 78 | className="sidebar__button sidebar__button--new-service" |
77 | data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`} | 79 | data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`} |
@@ -79,6 +81,7 @@ export default @observer class Sidebar extends Component { | |||
79 | <i className="mdi mdi-plus-box" /> | 81 | <i className="mdi mdi-plus-box" /> |
80 | </button> | 82 | </button> |
81 | <button | 83 | <button |
84 | type="button" | ||
82 | onClick={() => openSettings({ path: 'app' })} | 85 | onClick={() => openSettings({ path: 'app' })} |
83 | className="sidebar__button sidebar__button--settings" | 86 | className="sidebar__button sidebar__button--settings" |
84 | data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`} | 87 | data-tip={`${intl.formatMessage(messages.settings)} (${ctrlKey}+,)`} |
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js new file mode 100644 index 000000000..415a8d1b5 --- /dev/null +++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js | |||
@@ -0,0 +1,84 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import Button from '../../../ui/Button'; | ||
8 | |||
9 | import styles from './styles'; | ||
10 | |||
11 | const messages = defineMessages({ | ||
12 | headline: { | ||
13 | id: 'service.errorHandler.headline', | ||
14 | defaultMessage: '!!!Oh no!', | ||
15 | }, | ||
16 | text: { | ||
17 | id: 'service.errorHandler.text', | ||
18 | defaultMessage: '!!!{name} has failed to load.', | ||
19 | }, | ||
20 | action: { | ||
21 | id: 'service.errorHandler.action', | ||
22 | defaultMessage: '!!!Reload {name}', | ||
23 | }, | ||
24 | editAction: { | ||
25 | id: 'service.errorHandler.editAction', | ||
26 | defaultMessage: '!!!Edit {name}', | ||
27 | }, | ||
28 | errorMessage: { | ||
29 | id: 'service.errorHandler.message', | ||
30 | defaultMessage: '!!!Error:', | ||
31 | }, | ||
32 | }); | ||
33 | |||
34 | export default @injectSheet(styles) @observer class WebviewCrashHandler extends Component { | ||
35 | static propTypes = { | ||
36 | name: PropTypes.string.isRequired, | ||
37 | reload: PropTypes.func.isRequired, | ||
38 | edit: PropTypes.func.isRequired, | ||
39 | errorMessage: PropTypes.string.isRequired, | ||
40 | classes: PropTypes.object.isRequired, | ||
41 | }; | ||
42 | |||
43 | static contextTypes = { | ||
44 | intl: intlShape, | ||
45 | }; | ||
46 | |||
47 | render() { | ||
48 | const { | ||
49 | name, | ||
50 | reload, | ||
51 | edit, | ||
52 | errorMessage, | ||
53 | classes, | ||
54 | } = this.props; | ||
55 | const { intl } = this.context; | ||
56 | |||
57 | return ( | ||
58 | <div className={classes.component}> | ||
59 | <h1>{intl.formatMessage(messages.headline)}</h1> | ||
60 | <p>{intl.formatMessage(messages.text, { name })}</p> | ||
61 | <p> | ||
62 | <strong> | ||
63 | {intl.formatMessage(messages.errorMessage)} | ||
64 | : | ||
65 | </strong> | ||
66 | {' '} | ||
67 | {errorMessage} | ||
68 | </p> | ||
69 | <div className={classes.buttonContainer}> | ||
70 | <Button | ||
71 | label={intl.formatMessage(messages.editAction, { name })} | ||
72 | buttonType="inverted" | ||
73 | onClick={() => edit()} | ||
74 | /> | ||
75 | <Button | ||
76 | label={intl.formatMessage(messages.action, { name })} | ||
77 | buttonType="inverted" | ||
78 | onClick={() => reload()} | ||
79 | /> | ||
80 | </div> | ||
81 | </div> | ||
82 | ); | ||
83 | } | ||
84 | } | ||
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js new file mode 100644 index 000000000..f11386798 --- /dev/null +++ b/src/components/services/content/ErrorHandlers/styles.js | |||
@@ -0,0 +1,25 @@ | |||
1 | export default { | ||
2 | component: { | ||
3 | left: 0, | ||
4 | position: 'absolute', | ||
5 | top: 0, | ||
6 | width: '100%', | ||
7 | zIndex: 0, | ||
8 | alignItems: 'center', | ||
9 | // background: $theme-gray-lighter; | ||
10 | display: 'flex', | ||
11 | flexDirection: 'column', | ||
12 | justifyContent: 'center', | ||
13 | textAlign: 'center', | ||
14 | }, | ||
15 | buttonContainer: { | ||
16 | display: 'flex', | ||
17 | flexDirection: 'row', | ||
18 | height: 'auto', | ||
19 | margin: [40, 0, 20], | ||
20 | |||
21 | '& button': { | ||
22 | margin: [0, 10, 0, 10], | ||
23 | }, | ||
24 | }, | ||
25 | }; | ||
diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js index 58fb38d8c..d0f12256e 100644 --- a/src/components/services/content/ServiceDisabled.js +++ b/src/components/services/content/ServiceDisabled.js | |||
@@ -27,6 +27,7 @@ export default @observer class ServiceDisabled extends Component { | |||
27 | }; | 27 | }; |
28 | 28 | ||
29 | countdownInterval = null; | 29 | countdownInterval = null; |
30 | |||
30 | countdownIntervalTimeout = 1000; | 31 | countdownIntervalTimeout = 1000; |
31 | 32 | ||
32 | render() { | 33 | render() { |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 7163209ee..b1a2c0207 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { autorun } from 'mobx'; | 3 | import { autorun } from 'mobx'; |
4 | import { observer } from 'mobx-react'; | 4 | import { observer } from 'mobx-react'; |
@@ -7,7 +7,9 @@ import classnames from 'classnames'; | |||
7 | 7 | ||
8 | import ServiceModel from '../../../models/Service'; | 8 | import ServiceModel from '../../../models/Service'; |
9 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; | 9 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; |
10 | import WebviewLoader from '../../ui/WebviewLoader'; | ||
10 | import WebviewCrashHandler from './WebviewCrashHandler'; | 11 | import WebviewCrashHandler from './WebviewCrashHandler'; |
12 | import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | ||
11 | import ServiceDisabled from './ServiceDisabled'; | 13 | import ServiceDisabled from './ServiceDisabled'; |
12 | 14 | ||
13 | export default @observer class ServiceWebview extends Component { | 15 | export default @observer class ServiceWebview extends Component { |
@@ -15,8 +17,10 @@ export default @observer class ServiceWebview extends Component { | |||
15 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 17 | service: PropTypes.instanceOf(ServiceModel).isRequired, |
16 | setWebviewReference: PropTypes.func.isRequired, | 18 | setWebviewReference: PropTypes.func.isRequired, |
17 | reload: PropTypes.func.isRequired, | 19 | reload: PropTypes.func.isRequired, |
20 | edit: PropTypes.func.isRequired, | ||
18 | isAppMuted: PropTypes.bool.isRequired, | 21 | isAppMuted: PropTypes.bool.isRequired, |
19 | enable: PropTypes.func.isRequired, | 22 | enable: PropTypes.func.isRequired, |
23 | isActive: PropTypes.bool, | ||
20 | }; | 24 | }; |
21 | 25 | ||
22 | static defaultProps = { | 26 | static defaultProps = { |
@@ -29,8 +33,12 @@ export default @observer class ServiceWebview extends Component { | |||
29 | statusBarVisible: false, | 33 | statusBarVisible: false, |
30 | }; | 34 | }; |
31 | 35 | ||
36 | autorunDisposer = null; | ||
37 | |||
38 | webview = null; | ||
39 | |||
32 | componentDidMount() { | 40 | componentDidMount() { |
33 | autorun(() => { | 41 | this.autorunDisposer = autorun(() => { |
34 | if (this.props.service.isActive) { | 42 | if (this.props.service.isActive) { |
35 | this.setState({ forceRepaint: true }); | 43 | this.setState({ forceRepaint: true }); |
36 | setTimeout(() => { | 44 | setTimeout(() => { |
@@ -40,6 +48,10 @@ export default @observer class ServiceWebview extends Component { | |||
40 | }); | 48 | }); |
41 | } | 49 | } |
42 | 50 | ||
51 | componentWillUnmount() { | ||
52 | this.autorunDisposer(); | ||
53 | } | ||
54 | |||
43 | updateTargetUrl = (event) => { | 55 | updateTargetUrl = (event) => { |
44 | let visible = true; | 56 | let visible = true; |
45 | if (event.url === '' || event.url === '#') { | 57 | if (event.url === '' || event.url === '#') { |
@@ -51,13 +63,12 @@ export default @observer class ServiceWebview extends Component { | |||
51 | }); | 63 | }); |
52 | } | 64 | } |
53 | 65 | ||
54 | webview = null; | ||
55 | |||
56 | render() { | 66 | render() { |
57 | const { | 67 | const { |
58 | service, | 68 | service, |
59 | setWebviewReference, | 69 | setWebviewReference, |
60 | reload, | 70 | reload, |
71 | edit, | ||
61 | isAppMuted, | 72 | isAppMuted, |
62 | enable, | 73 | enable, |
63 | } = this.props; | 74 | } = this.props; |
@@ -78,25 +89,47 @@ export default @observer class ServiceWebview extends Component { | |||
78 | 89 | ||
79 | return ( | 90 | return ( |
80 | <div className={webviewClasses}> | 91 | <div className={webviewClasses}> |
81 | {service.hasCrashed && ( | 92 | {service.isActive && service.isEnabled && ( |
82 | <WebviewCrashHandler | 93 | <Fragment> |
83 | name={service.recipe.name} | 94 | {service.hasCrashed && ( |
84 | webview={service.webview} | 95 | <WebviewCrashHandler |
85 | reload={reload} | 96 | name={service.recipe.name} |
86 | /> | 97 | webview={service.webview} |
98 | reload={reload} | ||
99 | /> | ||
100 | )} | ||
101 | {service.isEnabled && service.isLoading && service.isFirstLoad && ( | ||
102 | <WebviewLoader | ||
103 | loaded={false} | ||
104 | name={service.name} | ||
105 | /> | ||
106 | )} | ||
107 | {service.isError && ( | ||
108 | <WebviewErrorHandler | ||
109 | name={service.recipe.name} | ||
110 | errorMessage={service.errorMessage} | ||
111 | reload={reload} | ||
112 | edit={edit} | ||
113 | /> | ||
114 | )} | ||
115 | </Fragment> | ||
87 | )} | 116 | )} |
88 | {!service.isEnabled ? ( | 117 | {!service.isEnabled ? ( |
89 | <ServiceDisabled | 118 | <Fragment> |
90 | name={service.recipe.name} | 119 | {service.isActive && ( |
91 | webview={service.webview} | 120 | <ServiceDisabled |
92 | enable={enable} | 121 | name={service.recipe.name} |
93 | /> | 122 | webview={service.webview} |
123 | enable={enable} | ||
124 | /> | ||
125 | )} | ||
126 | </Fragment> | ||
94 | ) : ( | 127 | ) : ( |
95 | <Webview | 128 | <Webview |
96 | ref={(element) => { this.webview = element; }} | 129 | ref={(element) => { this.webview = element; }} |
97 | autosize | 130 | autosize |
98 | src={service.url} | 131 | src={service.url} |
99 | preload="./webview/plugin.js" | 132 | preload="./webview/recipe.js" |
100 | partition={`persist:service-${service.id}`} | 133 | partition={`persist:service-${service.id}`} |
101 | onDidAttach={() => setWebviewReference({ | 134 | onDidAttach={() => setWebviewReference({ |
102 | serviceId: service.id, | 135 | serviceId: service.id, |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 4cbd51043..1aeb17e03 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -20,18 +20,18 @@ const messages = defineMessages({ | |||
20 | 20 | ||
21 | export default @observer class Services extends Component { | 21 | export default @observer class Services extends Component { |
22 | static propTypes = { | 22 | static propTypes = { |
23 | services: MobxPropTypes.arrayOrObservableArray.isRequired, | 23 | services: MobxPropTypes.arrayOrObservableArray, |
24 | setWebviewReference: PropTypes.func.isRequired, | 24 | setWebviewReference: PropTypes.func.isRequired, |
25 | handleIPCMessage: PropTypes.func.isRequired, | 25 | handleIPCMessage: PropTypes.func.isRequired, |
26 | openWindow: PropTypes.func.isRequired, | 26 | openWindow: PropTypes.func.isRequired, |
27 | reload: PropTypes.func.isRequired, | 27 | reload: PropTypes.func.isRequired, |
28 | openSettings: PropTypes.func.isRequired, | ||
28 | isAppMuted: PropTypes.bool.isRequired, | 29 | isAppMuted: PropTypes.bool.isRequired, |
29 | update: PropTypes.func.isRequired, | 30 | update: PropTypes.func.isRequired, |
30 | }; | 31 | }; |
31 | 32 | ||
32 | static defaultProps = { | 33 | static defaultProps = { |
33 | services: [], | 34 | services: [], |
34 | activeService: '', | ||
35 | }; | 35 | }; |
36 | 36 | ||
37 | static contextTypes = { | 37 | static contextTypes = { |
@@ -45,6 +45,7 @@ export default @observer class Services extends Component { | |||
45 | setWebviewReference, | 45 | setWebviewReference, |
46 | openWindow, | 46 | openWindow, |
47 | reload, | 47 | reload, |
48 | openSettings, | ||
48 | isAppMuted, | 49 | isAppMuted, |
49 | update, | 50 | update, |
50 | } = this.props; | 51 | } = this.props; |
@@ -79,6 +80,7 @@ export default @observer class Services extends Component { | |||
79 | setWebviewReference={setWebviewReference} | 80 | setWebviewReference={setWebviewReference} |
80 | openWindow={openWindow} | 81 | openWindow={openWindow} |
81 | reload={() => reload({ serviceId: service.id })} | 82 | reload={() => reload({ serviceId: service.id })} |
83 | edit={() => openSettings({ path: `services/edit/${service.id}` })} | ||
82 | isAppMuted={isAppMuted} | 84 | isAppMuted={isAppMuted} |
83 | enable={() => update({ | 85 | enable={() => update({ |
84 | serviceId: service.id, | 86 | serviceId: service.id, |
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js index 3be1fccf4..42bc3c877 100644 --- a/src/components/services/content/WebviewCrashHandler.js +++ b/src/components/services/content/WebviewCrashHandler.js | |||
@@ -38,13 +38,18 @@ export default @observer class WebviewCrashHandler extends Component { | |||
38 | countdown: 10000, | 38 | countdown: 10000, |
39 | } | 39 | } |
40 | 40 | ||
41 | countdownInterval = null; | ||
42 | |||
43 | countdownIntervalTimeout = 1000; | ||
44 | |||
45 | |||
41 | componentDidMount() { | 46 | componentDidMount() { |
42 | const { reload } = this.props; | 47 | const { reload } = this.props; |
43 | 48 | ||
44 | this.countdownInterval = setInterval(() => { | 49 | this.countdownInterval = setInterval(() => { |
45 | this.setState({ | 50 | this.setState(prevState => ({ |
46 | countdown: this.state.countdown - this.countdownIntervalTimeout, | 51 | countdown: prevState.countdown - this.countdownIntervalTimeout, |
47 | }); | 52 | })); |
48 | 53 | ||
49 | if (this.state.countdown <= 0) { | 54 | if (this.state.countdown <= 0) { |
50 | reload(); | 55 | reload(); |
@@ -53,9 +58,6 @@ export default @observer class WebviewCrashHandler extends Component { | |||
53 | }, this.countdownIntervalTimeout); | 58 | }, this.countdownIntervalTimeout); |
54 | } | 59 | } |
55 | 60 | ||
56 | countdownInterval = null; | ||
57 | countdownIntervalTimeout = 1000; | ||
58 | |||
59 | render() { | 61 | render() { |
60 | const { name, reload } = this.props; | 62 | const { name, reload } = this.props; |
61 | const { intl } = this.context; | 63 | const { intl } = this.context; |
@@ -70,10 +72,12 @@ export default @observer class WebviewCrashHandler extends Component { | |||
70 | buttonType="inverted" | 72 | buttonType="inverted" |
71 | onClick={() => reload()} | 73 | onClick={() => reload()} |
72 | /> | 74 | /> |
73 | <p className="footnote">{intl.formatMessage(messages.autoReload, { | 75 | <p className="footnote"> |
74 | name, | 76 | {intl.formatMessage(messages.autoReload, { |
75 | seconds: this.state.countdown / 1000, | 77 | name, |
76 | })}</p> | 78 | seconds: this.state.countdown / 1000, |
79 | })} | ||
80 | </p> | ||
77 | </div> | 81 | </div> |
78 | ); | 82 | ); |
79 | } | 83 | } |
diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js index 3cb08feb1..72ba7b2e3 100644 --- a/src/components/settings/SettingsLayout.js +++ b/src/components/settings/SettingsLayout.js | |||
@@ -2,6 +2,7 @@ import React, { Component } from 'react'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | 4 | ||
5 | import ErrorBoundary from '../util/ErrorBoundary'; | ||
5 | import { oneOrManyChildElements } from '../../prop-types'; | 6 | import { oneOrManyChildElements } from '../../prop-types'; |
6 | import Appear from '../ui/effects/Appear'; | 7 | import Appear from '../ui/effects/Appear'; |
7 | 8 | ||
@@ -36,18 +37,22 @@ export default @observer class SettingsLayout extends Component { | |||
36 | return ( | 37 | return ( |
37 | <Appear transitionName="fadeIn-fast"> | 38 | <Appear transitionName="fadeIn-fast"> |
38 | <div className="settings-wrapper"> | 39 | <div className="settings-wrapper"> |
39 | <button | 40 | <ErrorBoundary> |
40 | className="settings-wrapper__action" | ||
41 | onClick={closeSettings} | ||
42 | /> | ||
43 | <div className="settings franz-form"> | ||
44 | {navigation} | ||
45 | {children} | ||
46 | <button | 41 | <button |
47 | className="settings__close mdi mdi-close" | 42 | type="button" |
43 | className="settings-wrapper__action" | ||
48 | onClick={closeSettings} | 44 | onClick={closeSettings} |
49 | /> | 45 | /> |
50 | </div> | 46 | <div className="settings franz-form"> |
47 | {navigation} | ||
48 | {children} | ||
49 | <button | ||
50 | type="button" | ||
51 | className="settings__close mdi mdi-close" | ||
52 | onClick={closeSettings} | ||
53 | /> | ||
54 | </div> | ||
55 | </ErrorBoundary> | ||
51 | </div> | 56 | </div> |
52 | </Appear> | 57 | </Appear> |
53 | ); | 58 | ); |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 06c7074dd..9c9543749 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } 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 } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
@@ -132,21 +132,19 @@ export default @observer class AccountDashboard extends Component { | |||
132 | )} | 132 | )} |
133 | 133 | ||
134 | {!isLoading && userInfoRequestFailed && ( | 134 | {!isLoading && userInfoRequestFailed && ( |
135 | <div> | 135 | <Infobox |
136 | <Infobox | 136 | icon="alert" |
137 | icon="alert" | 137 | type="danger" |
138 | type="danger" | 138 | ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} |
139 | ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} | 139 | ctaLoading={isLoading} |
140 | ctaLoading={isLoading} | 140 | ctaOnClick={retryUserInfoRequest} |
141 | ctaOnClick={retryUserInfoRequest} | 141 | > |
142 | > | 142 | {intl.formatMessage(messages.userInfoRequestFailed)} |
143 | {intl.formatMessage(messages.userInfoRequestFailed)} | 143 | </Infobox> |
144 | </Infobox> | ||
145 | </div> | ||
146 | )} | 144 | )} |
147 | 145 | ||
148 | {!userInfoRequestFailed && ( | 146 | {!userInfoRequestFailed && ( |
149 | <div> | 147 | <Fragment> |
150 | {!isLoading && ( | 148 | {!isLoading && ( |
151 | <div className="account"> | 149 | <div className="account"> |
152 | <div className="account__box account__box--flex"> | 150 | <div className="account__box account__box--flex"> |
@@ -169,7 +167,8 @@ export default @observer class AccountDashboard extends Component { | |||
169 | {`${user.firstname} ${user.lastname}`} | 167 | {`${user.firstname} ${user.lastname}`} |
170 | </h2> | 168 | </h2> |
171 | {user.organization && `${user.organization}, `} | 169 | {user.organization && `${user.organization}, `} |
172 | {user.email}<br /> | 170 | {user.email} |
171 | <br /> | ||
173 | {!user.isEnterprise && !user.isPremium && ( | 172 | {!user.isEnterprise && !user.isPremium && ( |
174 | <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span> | 173 | <span className="badge badge">{intl.formatMessage(messages.accountTypeBasic)}</span> |
175 | )} | 174 | )} |
@@ -194,7 +193,7 @@ export default @observer class AccountDashboard extends Component { | |||
194 | ) : ( | 193 | ) : ( |
195 | <div className="account franz-form"> | 194 | <div className="account franz-form"> |
196 | {orders.length > 0 && ( | 195 | {orders.length > 0 && ( |
197 | <div> | 196 | <Fragment> |
198 | <div className="account__box"> | 197 | <div className="account__box"> |
199 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> | 198 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> |
200 | <div className="account__subscription"> | 199 | <div className="account__subscription"> |
@@ -219,6 +218,7 @@ export default @observer class AccountDashboard extends Component { | |||
219 | </td> | 218 | </td> |
220 | <td className="invoices__action"> | 219 | <td className="invoices__action"> |
221 | <button | 220 | <button |
221 | type="button" | ||
222 | onClick={() => openExternalUrl(order.invoiceUrl)} | 222 | onClick={() => openExternalUrl(order.invoiceUrl)} |
223 | > | 223 | > |
224 | {intl.formatMessage(messages.invoiceDownload)} | 224 | {intl.formatMessage(messages.invoiceDownload)} |
@@ -229,7 +229,7 @@ export default @observer class AccountDashboard extends Component { | |||
229 | </tbody> | 229 | </tbody> |
230 | </table> | 230 | </table> |
231 | </div> | 231 | </div> |
232 | </div> | 232 | </Fragment> |
233 | )} | 233 | )} |
234 | </div> | 234 | </div> |
235 | ) | 235 | ) |
@@ -262,20 +262,6 @@ export default @observer class AccountDashboard extends Component { | |||
262 | </div> | 262 | </div> |
263 | )} | 263 | )} |
264 | 264 | ||
265 | {user.isMiner && ( | ||
266 | <div className="account franz-form"> | ||
267 | <div className="account__box account__box"> | ||
268 | <h2>Miner Info</h2> | ||
269 | <div className="account__subscription"> | ||
270 | <div> | ||
271 | <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> | ||
272 | <p>Every financial support is still much appreciated.</p> | ||
273 | </div> | ||
274 | </div> | ||
275 | </div> | ||
276 | </div> | ||
277 | )} | ||
278 | |||
279 | {!user.isEnterprise && !user.isPremium && ( | 265 | {!user.isEnterprise && !user.isPremium && ( |
280 | isLoadingPlans ? ( | 266 | isLoadingPlans ? ( |
281 | <Loader /> | 267 | <Loader /> |
@@ -312,7 +298,7 @@ export default @observer class AccountDashboard extends Component { | |||
312 | </div> | 298 | </div> |
313 | </div> | 299 | </div> |
314 | )} | 300 | )} |
315 | </div> | 301 | </Fragment> |
316 | )} | 302 | )} |
317 | </div> | 303 | </div> |
318 | <ReactTooltip place="right" type="dark" effect="solid" /> | 304 | <ReactTooltip place="right" type="dark" effect="solid" /> |
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index b86d94ac7..953f702f8 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js | |||
@@ -59,7 +59,9 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp | |||
59 | className="settings-navigation__link" | 59 | className="settings-navigation__link" |
60 | activeClassName="is-active" | 60 | activeClassName="is-active" |
61 | > | 61 | > |
62 | {intl.formatMessage(messages.yourServices)} <span className="badge">{serviceCount}</span> | 62 | {intl.formatMessage(messages.yourServices)} |
63 | {' '} | ||
64 | <span className="badge">{serviceCount}</span> | ||
63 | </Link> | 65 | </Link> |
64 | <Link | 66 | <Link |
65 | to="/settings/user" | 67 | to="/settings/user" |
@@ -93,4 +95,3 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp | |||
93 | ); | 95 | ); |
94 | } | 96 | } |
95 | } | 97 | } |
96 | |||
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js index dae8891b3..3bb0852b2 100644 --- a/src/components/settings/recipes/RecipeItem.js +++ b/src/components/settings/recipes/RecipeItem.js | |||
@@ -15,6 +15,7 @@ export default @observer class RecipeItem extends Component { | |||
15 | 15 | ||
16 | return ( | 16 | return ( |
17 | <button | 17 | <button |
18 | type="button" | ||
18 | className="recipe-teaser" | 19 | className="recipe-teaser" |
19 | onClick={onClick} | 20 | onClick={onClick} |
20 | > | 21 | > |
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js index cd783200f..00cd725cf 100644 --- a/src/components/settings/recipes/RecipesDashboard.js +++ b/src/components/settings/recipes/RecipesDashboard.js | |||
@@ -129,11 +129,17 @@ export default @observer class RecipesDashboard extends Component { | |||
129 | activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} | 129 | activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} |
130 | onClick={() => resetSearch()} | 130 | onClick={() => resetSearch()} |
131 | > | 131 | > |
132 | {intl.formatMessage(messages.devRecipes)} ({devRecipesCount}) | 132 | {intl.formatMessage(messages.devRecipes)} |
133 | {' '} | ||
134 | ( | ||
135 | {devRecipesCount} | ||
136 | ) | ||
133 | </Link> | 137 | </Link> |
134 | )} | 138 | )} |
135 | <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> | 139 | <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> |
136 | {intl.formatMessage(messages.missingService)} <i className="mdi mdi-open-in-new" /> | 140 | {intl.formatMessage(messages.missingService)} |
141 | {' '} | ||
142 | <i className="mdi mdi-open-in-new" /> | ||
137 | </a> | 143 | </a> |
138 | </div> | 144 | </div> |
139 | {/* )} */} | 145 | {/* )} */} |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index d16ec35b8..468d85c45 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
@@ -14,6 +14,7 @@ import Input from '../../ui/Input'; | |||
14 | import Toggle from '../../ui/Toggle'; | 14 | import Toggle from '../../ui/Toggle'; |
15 | import Button from '../../ui/Button'; | 15 | import Button from '../../ui/Button'; |
16 | import ImageUpload from '../../ui/ImageUpload'; | 16 | import ImageUpload from '../../ui/ImageUpload'; |
17 | import Select from '../../ui/Select'; | ||
17 | 18 | ||
18 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | 19 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; |
19 | 20 | ||
@@ -96,7 +97,11 @@ const messages = defineMessages({ | |||
96 | }, | 97 | }, |
97 | headlineProxy: { | 98 | headlineProxy: { |
98 | id: 'settings.service.form.proxy.headline', | 99 | id: 'settings.service.form.proxy.headline', |
99 | defaultMessage: '!!!Proxy Settings', | 100 | defaultMessage: '!!!HTTP/HTTPS Proxy Settings', |
101 | }, | ||
102 | proxyRestartInfo: { | ||
103 | id: 'settings.service.form.proxy.restartInfo', | ||
104 | defaultMessage: '!!!Please restart Franz after changing proxy Settings.', | ||
100 | }, | 105 | }, |
101 | proxyInfo: { | 106 | proxyInfo: { |
102 | id: 'settings.service.form.proxy.info', | 107 | id: 'settings.service.form.proxy.info', |
@@ -129,6 +134,7 @@ export default @observer class EditServiceForm extends Component { | |||
129 | static defaultProps = { | 134 | static defaultProps = { |
130 | service: {}, | 135 | service: {}, |
131 | }; | 136 | }; |
137 | |||
132 | static contextTypes = { | 138 | static contextTypes = { |
133 | intl: intlShape, | 139 | intl: intlShape, |
134 | }; | 140 | }; |
@@ -270,14 +276,14 @@ export default @observer class EditServiceForm extends Component { | |||
270 | {recipe.hasCustomUrl && ( | 276 | {recipe.hasCustomUrl && ( |
271 | <TabItem title={intl.formatMessage(messages.tabOnPremise)}> | 277 | <TabItem title={intl.formatMessage(messages.tabOnPremise)}> |
272 | {user.isPremium || recipe.author.find(a => a.email === user.email) ? ( | 278 | {user.isPremium || recipe.author.find(a => a.email === user.email) ? ( |
273 | <div> | 279 | <Fragment> |
274 | <Input field={form.$('customUrl')} /> | 280 | <Input field={form.$('customUrl')} /> |
275 | {form.error === 'url-validation-error' && ( | 281 | {form.error === 'url-validation-error' && ( |
276 | <p className="franz-form__error"> | 282 | <p className="franz-form__error"> |
277 | {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} | 283 | {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} |
278 | </p> | 284 | </p> |
279 | )} | 285 | )} |
280 | </div> | 286 | </Fragment> |
281 | ) : ( | 287 | ) : ( |
282 | <div className="center premium-info"> | 288 | <div className="center premium-info"> |
283 | <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p> | 289 | <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p> |
@@ -307,12 +313,12 @@ export default @observer class EditServiceForm extends Component { | |||
307 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> | 313 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> |
308 | <Toggle field={form.$('isBadgeEnabled')} /> | 314 | <Toggle field={form.$('isBadgeEnabled')} /> |
309 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( | 315 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( |
310 | <div> | 316 | <Fragment> |
311 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | 317 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> |
312 | <p className="settings__help"> | 318 | <p className="settings__help"> |
313 | {intl.formatMessage(messages.indirectMessageInfo)} | 319 | {intl.formatMessage(messages.indirectMessageInfo)} |
314 | </p> | 320 | </p> |
315 | </div> | 321 | </Fragment> |
316 | )} | 322 | )} |
317 | </div> | 323 | </div> |
318 | 324 | ||
@@ -333,6 +339,12 @@ export default @observer class EditServiceForm extends Component { | |||
333 | </div> | 339 | </div> |
334 | </div> | 340 | </div> |
335 | 341 | ||
342 | <PremiumFeatureContainer> | ||
343 | <div className="settings__settings-group"> | ||
344 | <Select field={form.$('spellcheckerLanguage')} /> | ||
345 | </div> | ||
346 | </PremiumFeatureContainer> | ||
347 | |||
336 | {isProxyFeatureEnabled && ( | 348 | {isProxyFeatureEnabled && ( |
337 | <PremiumFeatureContainer condition={isProxyFeaturePremiumFeature}> | 349 | <PremiumFeatureContainer condition={isProxyFeaturePremiumFeature}> |
338 | <div className="settings__settings-group"> | 350 | <div className="settings__settings-group"> |
@@ -342,18 +354,31 @@ export default @observer class EditServiceForm extends Component { | |||
342 | </h3> | 354 | </h3> |
343 | <Toggle field={form.$('proxy.isEnabled')} /> | 355 | <Toggle field={form.$('proxy.isEnabled')} /> |
344 | {form.$('proxy.isEnabled').value && ( | 356 | {form.$('proxy.isEnabled').value && ( |
345 | <div> | 357 | <Fragment> |
346 | <Input field={form.$('proxy.host')} /> | 358 | <div className="grid"> |
347 | <Input field={form.$('proxy.user')} /> | 359 | <div className="grid__row"> |
348 | <Input | 360 | <Input field={form.$('proxy.host')} className="proxyHost" /> |
349 | field={form.$('proxy.password')} | 361 | <Input field={form.$('proxy.port')} /> |
350 | showPasswordToggle | 362 | </div> |
351 | /> | 363 | </div> |
364 | <div className="grid"> | ||
365 | <div className="grid__row"> | ||
366 | <Input field={form.$('proxy.user')} /> | ||
367 | <Input | ||
368 | field={form.$('proxy.password')} | ||
369 | showPasswordToggle | ||
370 | /> | ||
371 | </div> | ||
372 | </div> | ||
373 | <p> | ||
374 | <span className="mdi mdi-information" /> | ||
375 | {intl.formatMessage(messages.proxyRestartInfo)} | ||
376 | </p> | ||
352 | <p> | 377 | <p> |
353 | <span className="mdi mdi-information" /> | 378 | <span className="mdi mdi-information" /> |
354 | {intl.formatMessage(messages.proxyInfo)} | 379 | {intl.formatMessage(messages.proxyInfo)} |
355 | </p> | 380 | </p> |
356 | </div> | 381 | </Fragment> |
357 | )} | 382 | )} |
358 | </div> | 383 | </div> |
359 | </PremiumFeatureContainer> | 384 | </PremiumFeatureContainer> |
diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js index 84080519b..ebc618a00 100644 --- a/src/components/settings/services/ServiceItem.js +++ b/src/components/settings/services/ServiceItem.js | |||
@@ -27,6 +27,7 @@ export default @observer class ServiceItem extends Component { | |||
27 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 27 | service: PropTypes.instanceOf(ServiceModel).isRequired, |
28 | goToServiceForm: PropTypes.func.isRequired, | 28 | goToServiceForm: PropTypes.func.isRequired, |
29 | }; | 29 | }; |
30 | |||
30 | static contextTypes = { | 31 | static contextTypes = { |
31 | intl: intlShape, | 32 | intl: intlShape, |
32 | }; | 33 | }; |
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js index e7dfaf106..a12df7372 100644 --- a/src/components/settings/services/ServicesDashboard.js +++ b/src/components/settings/services/ServicesDashboard.js | |||
@@ -101,17 +101,15 @@ export default @observer class ServicesDashboard extends Component { | |||
101 | /> | 101 | /> |
102 | )} | 102 | )} |
103 | {!isLoading && servicesRequestFailed && ( | 103 | {!isLoading && servicesRequestFailed && ( |
104 | <div> | 104 | <Infobox |
105 | <Infobox | 105 | icon="alert" |
106 | icon="alert" | 106 | type="danger" |
107 | type="danger" | 107 | ctaLabel={intl.formatMessage(messages.tryReloadServices)} |
108 | ctaLabel={intl.formatMessage(messages.tryReloadServices)} | 108 | ctaLoading={isLoading} |
109 | ctaLoading={isLoading} | 109 | ctaOnClick={retryServicesRequest} |
110 | ctaOnClick={retryServicesRequest} | 110 | > |
111 | > | 111 | {intl.formatMessage(messages.servicesRequestFailed)} |
112 | {intl.formatMessage(messages.servicesRequestFailed)} | 112 | </Infobox> |
113 | </Infobox> | ||
114 | </div> | ||
115 | )} | 113 | )} |
116 | 114 | ||
117 | {status.length > 0 && status.includes('updated') && ( | 115 | {status.length > 0 && status.includes('updated') && ( |
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 1ec2ab614..a92e559f3 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js | |||
@@ -1,5 +1,5 @@ | |||
1 | import { remote } from 'electron'; | 1 | import { remote } from 'electron'; |
2 | import React, { Component } from 'react'; | 2 | import React, { Component, Fragment } from 'react'; |
3 | import PropTypes from 'prop-types'; | 3 | import PropTypes from 'prop-types'; |
4 | import { observer } from 'mobx-react'; | 4 | import { observer } from 'mobx-react'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
@@ -171,21 +171,23 @@ export default @observer class EditSettingsForm extends Component { | |||
171 | <PremiumFeatureContainer | 171 | <PremiumFeatureContainer |
172 | condition={isSpellcheckerPremiumFeature} | 172 | condition={isSpellcheckerPremiumFeature} |
173 | > | 173 | > |
174 | <div> | 174 | <Fragment> |
175 | <Toggle | 175 | <Toggle |
176 | field={form.$('enableSpellchecking')} | 176 | field={form.$('enableSpellchecking')} |
177 | /> | 177 | /> |
178 | {form.$('enableSpellchecking').value && ( | 178 | {form.$('enableSpellchecking').value && ( |
179 | <Select field={form.$('spellcheckerLanguage')} /> | 179 | <Select field={form.$('spellcheckerLanguage')} /> |
180 | )} | 180 | )} |
181 | </div> | 181 | </Fragment> |
182 | </PremiumFeatureContainer> | 182 | </PremiumFeatureContainer> |
183 | <a | 183 | <a |
184 | href={FRANZ_TRANSLATION} | 184 | href={FRANZ_TRANSLATION} |
185 | target="_blank" | 185 | target="_blank" |
186 | className="link" | 186 | className="link" |
187 | > | 187 | > |
188 | {intl.formatMessage(messages.translationHelp)} <i className="mdi mdi-open-in-new" /> | 188 | {intl.formatMessage(messages.translationHelp)} |
189 | {' '} | ||
190 | <i className="mdi mdi-open-in-new" /> | ||
189 | </a> | 191 | </a> |
190 | 192 | ||
191 | {/* Advanced */} | 193 | {/* Advanced */} |
@@ -233,7 +235,9 @@ export default @observer class EditSettingsForm extends Component { | |||
233 | )} | 235 | )} |
234 | <br /> | 236 | <br /> |
235 | <Toggle field={form.$('beta')} /> | 237 | <Toggle field={form.$('beta')} /> |
236 | {intl.formatMessage(messages.currentVersion)} {remote.app.getVersion()} | 238 | {intl.formatMessage(messages.currentVersion)} |
239 | {' '} | ||
240 | {remote.app.getVersion()} | ||
237 | </form> | 241 | </form> |
238 | </div> | 242 | </div> |
239 | </div> | 243 | </div> |
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js index b825f844a..0e3ac6b10 100644 --- a/src/components/settings/user/EditUserForm.js +++ b/src/components/settings/user/EditUserForm.js | |||
@@ -48,10 +48,6 @@ export default @observer class EditServiceForm extends Component { | |||
48 | isEnterprise: PropTypes.bool.isRequired, | 48 | isEnterprise: PropTypes.bool.isRequired, |
49 | }; | 49 | }; |
50 | 50 | ||
51 | static defaultProps = { | ||
52 | service: {}, | ||
53 | }; | ||
54 | |||
55 | static contextTypes = { | 51 | static contextTypes = { |
56 | intl: intlShape, | 52 | intl: intlShape, |
57 | }; | 53 | }; |
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js index 12e8471ff..90da8ddc3 100644 --- a/src/components/subscription/SubscriptionForm.js +++ b/src/components/subscription/SubscriptionForm.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } 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 } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
@@ -81,8 +81,7 @@ export default @observer class SubscriptionForm extends Component { | |||
81 | hideInfo: PropTypes.bool.isRequired, | 81 | hideInfo: PropTypes.bool.isRequired, |
82 | }; | 82 | }; |
83 | 83 | ||
84 | static defaultProps ={ | 84 | static defaultProps = { |
85 | content: '', | ||
86 | showSkipOption: false, | 85 | showSkipOption: false, |
87 | skipAction: () => null, | 86 | skipAction: () => null, |
88 | skipButtonLabel: '', | 87 | skipButtonLabel: '', |
@@ -158,35 +157,33 @@ export default @observer class SubscriptionForm extends Component { | |||
158 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> | 157 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> |
159 | {!hideInfo && ( | 158 | {!hideInfo && ( |
160 | <div className="subscription__premium-info"> | 159 | <div className="subscription__premium-info"> |
161 | <div> | 160 | <p> |
162 | <p> | 161 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> |
163 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> | 162 | </p> |
164 | </p> | 163 | <div className="subscription"> |
165 | <div className="subscription"> | 164 | <ul className="subscription__premium-features"> |
166 | <ul className="subscription__premium-features"> | 165 | <li>{intl.formatMessage(messages.features.onpremise)}</li> |
167 | <li>{intl.formatMessage(messages.features.onpremise)}</li> | 166 | <li> |
168 | <li> | 167 | {intl.formatMessage(messages.features.noInterruptions)} |
169 | {intl.formatMessage(messages.features.noInterruptions)} | 168 | </li> |
170 | </li> | 169 | <li> |
171 | <li> | 170 | {intl.formatMessage(messages.features.spellchecker)} |
172 | {intl.formatMessage(messages.features.spellchecker)} | 171 | </li> |
173 | </li> | 172 | <li> |
174 | <li> | 173 | {intl.formatMessage(messages.features.proxy)} |
175 | {intl.formatMessage(messages.features.proxy)} | 174 | </li> |
176 | </li> | 175 | <li> |
177 | <li> | 176 | {intl.formatMessage(messages.features.ads)} |
178 | {intl.formatMessage(messages.features.ads)} | 177 | </li> |
179 | </li> | 178 | </ul> |
180 | </ul> | ||
181 | </div> | ||
182 | </div> | 179 | </div> |
183 | </div> | 180 | </div> |
184 | )} | 181 | )} |
185 | <div> | 182 | <Fragment> |
186 | {error.code === 'no-payment-session' && ( | 183 | {error.code === 'no-payment-session' && ( |
187 | <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p> | 184 | <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p> |
188 | )} | 185 | )} |
189 | </div> | 186 | </Fragment> |
190 | {showSkipOption && this.form.$('paymentTier').value === 'skip' ? ( | 187 | {showSkipOption && this.form.$('paymentTier').value === 'skip' ? ( |
191 | <Button | 188 | <Button |
192 | label={skipButtonLabel} | 189 | label={skipButtonLabel} |
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js index f3c63e7ee..b5d7c4b2d 100644 --- a/src/components/subscription/SubscriptionPopup.js +++ b/src/components/subscription/SubscriptionPopup.js | |||
@@ -46,7 +46,9 @@ export default @observer class SubscriptionPopup extends Component { | |||
46 | } | 46 | } |
47 | 47 | ||
48 | render() { | 48 | render() { |
49 | const { url, closeWindow, completeCheck, isCompleted } = this.props; | 49 | const { |
50 | url, closeWindow, completeCheck, isCompleted, | ||
51 | } = this.props; | ||
50 | const { intl } = this.context; | 52 | const { intl } = this.context; |
51 | 53 | ||
52 | return ( | 54 | return ( |
diff --git a/src/components/ui/AppLoader.js b/src/components/ui/AppLoader.js deleted file mode 100644 index ac3cdcb05..000000000 --- a/src/components/ui/AppLoader.js +++ /dev/null | |||
@@ -1,15 +0,0 @@ | |||
1 | import React from 'react'; | ||
2 | |||
3 | import Appear from '../../components/ui/effects/Appear'; | ||
4 | import Loader from '../../components/ui/Loader'; | ||
5 | |||
6 | export default function () { | ||
7 | return ( | ||
8 | <div className="app-loader"> | ||
9 | <Appear> | ||
10 | <h1 className="app-loader__title">Franz</h1> | ||
11 | <Loader color="#FFF" /> | ||
12 | </Appear> | ||
13 | </div> | ||
14 | ); | ||
15 | } | ||
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js new file mode 100644 index 000000000..61053f6d1 --- /dev/null +++ b/src/components/ui/AppLoader/index.js | |||
@@ -0,0 +1,70 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import injectSheet, { withTheme } from 'react-jss'; | ||
4 | import classnames from 'classnames'; | ||
5 | |||
6 | import FullscreenLoader from '../FullscreenLoader'; | ||
7 | import { shuffleArray } from '../../../helpers/array-helpers'; | ||
8 | |||
9 | import styles from './styles'; | ||
10 | |||
11 | const textList = shuffleArray([ | ||
12 | 'Looking for Sisi', | ||
13 | 'Contacting the herald', | ||
14 | 'Saddling the unicorn', | ||
15 | 'Learning the Waltz', | ||
16 | 'Visiting Horst & Grete', | ||
17 | 'Twisting my moustache', | ||
18 | 'Playing the trumpet', | ||
19 | 'Traveling through space & time', | ||
20 | ]); | ||
21 | |||
22 | export default @injectSheet(styles) @withTheme class AppLoader extends Component { | ||
23 | static propTypes = { | ||
24 | classes: PropTypes.object.isRequired, | ||
25 | theme: PropTypes.object.isRequired, | ||
26 | } | ||
27 | |||
28 | state = { | ||
29 | step: 0, | ||
30 | } | ||
31 | |||
32 | interval = null; | ||
33 | |||
34 | componentDidMount() { | ||
35 | this.interval = setInterval(() => { | ||
36 | this.setState(prevState => ({ | ||
37 | step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1, | ||
38 | })); | ||
39 | }, 2500); | ||
40 | } | ||
41 | |||
42 | componentWillUnmount() { | ||
43 | clearInterval(this.interval); | ||
44 | } | ||
45 | |||
46 | render() { | ||
47 | const { classes, theme } = this.props; | ||
48 | const { step } = this.state; | ||
49 | |||
50 | return ( | ||
51 | <FullscreenLoader | ||
52 | title="Franz" | ||
53 | className={classes.component} | ||
54 | spinnerColor={theme.colorAppLoaderSpinner} | ||
55 | > | ||
56 | {textList.map((text, i) => ( | ||
57 | <span | ||
58 | key={text} | ||
59 | className={classnames({ | ||
60 | [`${classes.slogan}`]: true, | ||
61 | [`${classes.visible}`]: step === i, | ||
62 | })} | ||
63 | > | ||
64 | {text} | ||
65 | </span> | ||
66 | ))} | ||
67 | </FullscreenLoader> | ||
68 | ); | ||
69 | } | ||
70 | } | ||
diff --git a/src/components/ui/AppLoader/styles.js b/src/components/ui/AppLoader/styles.js new file mode 100644 index 000000000..755a56b40 --- /dev/null +++ b/src/components/ui/AppLoader/styles.js | |||
@@ -0,0 +1,16 @@ | |||
1 | export default { | ||
2 | component: { | ||
3 | color: '#FFF', | ||
4 | }, | ||
5 | slogan: { | ||
6 | display: 'block', | ||
7 | opacity: 0, | ||
8 | transition: 'opacity 1s ease', | ||
9 | position: 'absolute', | ||
10 | textAlign: 'center', | ||
11 | width: '100%', | ||
12 | }, | ||
13 | visible: { | ||
14 | opacity: 1, | ||
15 | }, | ||
16 | }; | ||
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js index 309e05bb4..ffc7f7051 100644 --- a/src/components/ui/Button.js +++ b/src/components/ui/Button.js | |||
@@ -62,6 +62,8 @@ export default @observer class Button extends Component { | |||
62 | } | 62 | } |
63 | 63 | ||
64 | return ( | 64 | return ( |
65 | // disabling rule as button has type defined in `buttonProps` | ||
66 | /* eslint-disable react/button-has-type */ | ||
65 | <button {...buttonProps}> | 67 | <button {...buttonProps}> |
66 | <Loader | 68 | <Loader |
67 | loaded={loaded} | 69 | loaded={loaded} |
@@ -72,6 +74,7 @@ export default @observer class Button extends Component { | |||
72 | /> | 74 | /> |
73 | {label} | 75 | {label} |
74 | </button> | 76 | </button> |
77 | /* eslint-enable react/button-has-type */ | ||
75 | ); | 78 | ); |
76 | } | 79 | } |
77 | } | 80 | } |
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js new file mode 100644 index 000000000..6ecf4d395 --- /dev/null +++ b/src/components/ui/FullscreenLoader/index.js | |||
@@ -0,0 +1,56 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet, { withTheme } from 'react-jss'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import Loader from '../Loader'; | ||
8 | |||
9 | import styles from './styles'; | ||
10 | |||
11 | export default @observer @withTheme @injectSheet(styles) class FullscreenLoader extends Component { | ||
12 | static propTypes = { | ||
13 | className: PropTypes.string, | ||
14 | title: PropTypes.string.isRequired, | ||
15 | classes: PropTypes.object.isRequired, | ||
16 | theme: PropTypes.object.isRequired, | ||
17 | spinnerColor: PropTypes.string, | ||
18 | children: PropTypes.node, | ||
19 | } | ||
20 | |||
21 | static defaultProps = { | ||
22 | className: null, | ||
23 | spinnerColor: null, | ||
24 | children: null, | ||
25 | } | ||
26 | |||
27 | render() { | ||
28 | const { | ||
29 | classes, | ||
30 | title, | ||
31 | children, | ||
32 | spinnerColor, | ||
33 | className, | ||
34 | theme, | ||
35 | } = this.props; | ||
36 | |||
37 | return ( | ||
38 | <div className={classes.wrapper}> | ||
39 | <div | ||
40 | className={classnames({ | ||
41 | [`${classes.component}`]: true, | ||
42 | [`${className}`]: className, | ||
43 | })} | ||
44 | > | ||
45 | <h1 className={classes.title}>{title}</h1> | ||
46 | <Loader color={spinnerColor || theme.colorFullscreenLoaderSpinner} /> | ||
47 | {children && ( | ||
48 | <div className={classes.content}> | ||
49 | {children} | ||
50 | </div> | ||
51 | )} | ||
52 | </div> | ||
53 | </div> | ||
54 | ); | ||
55 | } | ||
56 | } | ||
diff --git a/src/components/ui/FullscreenLoader/styles.js b/src/components/ui/FullscreenLoader/styles.js new file mode 100644 index 000000000..64d24e4ce --- /dev/null +++ b/src/components/ui/FullscreenLoader/styles.js | |||
@@ -0,0 +1,23 @@ | |||
1 | export default { | ||
2 | wrapper: { | ||
3 | display: 'flex', | ||
4 | alignItems: 'center', | ||
5 | position: 'absolute', | ||
6 | width: '100%', | ||
7 | }, | ||
8 | component: { | ||
9 | width: '100%', | ||
10 | display: 'flex', | ||
11 | flexDirection: 'column', | ||
12 | alignItems: 'center', | ||
13 | textAlign: 'center', | ||
14 | height: 'auto', | ||
15 | }, | ||
16 | title: { | ||
17 | fontSize: 35, | ||
18 | }, | ||
19 | content: { | ||
20 | marginTop: 20, | ||
21 | width: '100%', | ||
22 | }, | ||
23 | }; | ||
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js index 76f77d631..83a05554b 100644 --- a/src/components/ui/ImageUpload.js +++ b/src/components/ui/ImageUpload.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { Field } from 'mobx-react-form'; | 4 | import { Field } from 'mobx-react-form'; |
@@ -23,6 +23,8 @@ export default @observer class ImageUpload extends Component { | |||
23 | path: null, | 23 | path: null, |
24 | } | 24 | } |
25 | 25 | ||
26 | dropzoneRef = null; | ||
27 | |||
26 | onDrop(acceptedFiles) { | 28 | onDrop(acceptedFiles) { |
27 | const { field } = this.props; | 29 | const { field } = this.props; |
28 | 30 | ||
@@ -36,8 +38,6 @@ export default @observer class ImageUpload extends Component { | |||
36 | field.set(''); | 38 | field.set(''); |
37 | } | 39 | } |
38 | 40 | ||
39 | dropzoneRef = null; | ||
40 | |||
41 | render() { | 41 | render() { |
42 | const { | 42 | const { |
43 | field, | 43 | field, |
@@ -57,7 +57,7 @@ export default @observer class ImageUpload extends Component { | |||
57 | <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label> | 57 | <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label> |
58 | <div className="image-upload"> | 58 | <div className="image-upload"> |
59 | {(field.value && field.value !== 'delete') || this.state.path ? ( | 59 | {(field.value && field.value !== 'delete') || this.state.path ? ( |
60 | <div> | 60 | <Fragment> |
61 | <div | 61 | <div |
62 | className="image-upload__preview" | 62 | className="image-upload__preview" |
63 | style={({ | 63 | style={({ |
@@ -84,7 +84,7 @@ export default @observer class ImageUpload extends Component { | |||
84 | </button> | 84 | </button> |
85 | <div className="image-upload__action-background" /> | 85 | <div className="image-upload__action-background" /> |
86 | </div> | 86 | </div> |
87 | </div> | 87 | </Fragment> |
88 | ) : ( | 88 | ) : ( |
89 | <Dropzone | 89 | <Dropzone |
90 | ref={(node) => { this.dropzoneRef = node; }} | 90 | ref={(node) => { this.dropzoneRef = node; }} |
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js index 94a1ddf76..612399e9f 100644 --- a/src/components/ui/InfoBar.js +++ b/src/components/ui/InfoBar.js | |||
@@ -5,7 +5,7 @@ import classnames from 'classnames'; | |||
5 | import Loader from 'react-loader'; | 5 | import Loader from 'react-loader'; |
6 | 6 | ||
7 | // import { oneOrManyChildElements } from '../../prop-types'; | 7 | // import { oneOrManyChildElements } from '../../prop-types'; |
8 | import Appear from '../ui/effects/Appear'; | 8 | import Appear from './effects/Appear'; |
9 | 9 | ||
10 | export default @observer class InfoBar extends Component { | 10 | export default @observer class InfoBar extends Component { |
11 | static propTypes = { | 11 | static propTypes = { |
@@ -64,6 +64,7 @@ export default @observer class InfoBar extends Component { | |||
64 | {children} | 64 | {children} |
65 | {ctaLabel && ( | 65 | {ctaLabel && ( |
66 | <button | 66 | <button |
67 | type="button" | ||
67 | className="info-bar__cta" | 68 | className="info-bar__cta" |
68 | onClick={onClick} | 69 | onClick={onClick} |
69 | > | 70 | > |
@@ -80,6 +81,7 @@ export default @observer class InfoBar extends Component { | |||
80 | </div> | 81 | </div> |
81 | {!sticky && ( | 82 | {!sticky && ( |
82 | <button | 83 | <button |
84 | type="button" | ||
83 | className="info-bar__close mdi mdi-close" | 85 | className="info-bar__close mdi mdi-close" |
84 | onClick={onHide} | 86 | onClick={onHide} |
85 | /> | 87 | /> |
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js index 77051f567..a33c6474a 100644 --- a/src/components/ui/Infobox.js +++ b/src/components/ui/Infobox.js | |||
@@ -61,6 +61,7 @@ export default @observer class Infobox extends Component { | |||
61 | <button | 61 | <button |
62 | className="infobox__cta" | 62 | className="infobox__cta" |
63 | onClick={ctaOnClick} | 63 | onClick={ctaOnClick} |
64 | type="button" | ||
64 | > | 65 | > |
65 | <Loader | 66 | <Loader |
66 | loaded={!ctaLoading} | 67 | loaded={!ctaLoading} |
@@ -74,6 +75,7 @@ export default @observer class Infobox extends Component { | |||
74 | )} | 75 | )} |
75 | {dismissable && ( | 76 | {dismissable && ( |
76 | <button | 77 | <button |
78 | type="button" | ||
77 | onClick={() => this.setState({ | 79 | onClick={() => this.setState({ |
78 | dismissed: true, | 80 | dismissed: true, |
79 | })} | 81 | })} |
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js index 7bf6e1b00..9b070c4df 100644 --- a/src/components/ui/Input.js +++ b/src/components/ui/Input.js | |||
@@ -33,6 +33,8 @@ export default @observer class Input extends Component { | |||
33 | passwordScore: 0, | 33 | passwordScore: 0, |
34 | } | 34 | } |
35 | 35 | ||
36 | inputElement = null; | ||
37 | |||
36 | componentDidMount() { | 38 | componentDidMount() { |
37 | if (this.props.focus) { | 39 | if (this.props.focus) { |
38 | this.focus(); | 40 | this.focus(); |
@@ -53,8 +55,6 @@ export default @observer class Input extends Component { | |||
53 | this.inputElement.focus(); | 55 | this.inputElement.focus(); |
54 | } | 56 | } |
55 | 57 | ||
56 | inputElement = null; | ||
57 | |||
58 | render() { | 58 | render() { |
59 | const { | 59 | const { |
60 | field, | 60 | field, |
@@ -110,7 +110,7 @@ export default @observer class Input extends Component { | |||
110 | 'mdi-eye': !this.state.showPassword, | 110 | 'mdi-eye': !this.state.showPassword, |
111 | 'mdi-eye-off': this.state.showPassword, | 111 | 'mdi-eye-off': this.state.showPassword, |
112 | })} | 112 | })} |
113 | onClick={() => this.setState({ showPassword: !this.state.showPassword })} | 113 | onClick={() => this.setState(prevState => ({ showPassword: !prevState.showPassword }))} |
114 | tabIndex="-1" | 114 | tabIndex="-1" |
115 | /> | 115 | /> |
116 | )} | 116 | )} |
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js index 0602290f1..b88686d5e 100644 --- a/src/components/ui/Link.js +++ b/src/components/ui/Link.js | |||
@@ -72,5 +72,4 @@ Link.wrappedComponent.defaultProps = { | |||
72 | activeClassName: '', | 72 | activeClassName: '', |
73 | strictFilter: false, | 73 | strictFilter: false, |
74 | target: '', | 74 | target: '', |
75 | openInBrowser: false, | ||
76 | }; | 75 | }; |
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js index 73984be94..67cd6af0b 100644 --- a/src/components/ui/PremiumFeatureContainer/index.js +++ b/src/components/ui/PremiumFeatureContainer/index.js | |||
@@ -73,4 +73,3 @@ PremiumFeatureContainer.wrappedComponent.propTypes = { | |||
73 | }).isRequired, | 73 | }).isRequired, |
74 | }).isRequired, | 74 | }).isRequired, |
75 | }; | 75 | }; |
76 | |||
diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js index 16c40d0ec..81d6666c6 100644 --- a/src/components/ui/PremiumFeatureContainer/styles.js +++ b/src/components/ui/PremiumFeatureContainer/styles.js | |||
@@ -5,6 +5,7 @@ export default theme => ({ | |||
5 | margin: [0, 0, 20, -20], | 5 | margin: [0, 0, 20, -20], |
6 | padding: 20, | 6 | padding: 20, |
7 | 'border-radius': theme.borderRadius, | 7 | 'border-radius': theme.borderRadius, |
8 | pointerEvents: 'none', | ||
8 | }, | 9 | }, |
9 | titleContainer: { | 10 | titleContainer: { |
10 | display: 'flex', | 11 | display: 'flex', |
@@ -20,6 +21,7 @@ export default theme => ({ | |||
20 | 'border-radius': theme.borderRadiusSmall, | 21 | 'border-radius': theme.borderRadiusSmall, |
21 | padding: [2, 4], | 22 | padding: [2, 4], |
22 | 'font-size': 12, | 23 | 'font-size': 12, |
24 | pointerEvents: 'initial', | ||
23 | }, | 25 | }, |
24 | content: { | 26 | content: { |
25 | opacity: 0.5, | 27 | opacity: 0.5, |
diff --git a/src/components/ui/Radio.js b/src/components/ui/Radio.js index 63ca6f9b8..ba13aca63 100644 --- a/src/components/ui/Radio.js +++ b/src/components/ui/Radio.js | |||
@@ -18,6 +18,8 @@ export default @observer class Radio extends Component { | |||
18 | showLabel: true, | 18 | showLabel: true, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | inputElement = null; | ||
22 | |||
21 | componentDidMount() { | 23 | componentDidMount() { |
22 | if (this.props.focus) { | 24 | if (this.props.focus) { |
23 | this.focus(); | 25 | this.focus(); |
@@ -28,8 +30,6 @@ export default @observer class Radio extends Component { | |||
28 | this.inputElement.focus(); | 30 | this.inputElement.focus(); |
29 | } | 31 | } |
30 | 32 | ||
31 | inputElement = null; | ||
32 | |||
33 | render() { | 33 | render() { |
34 | const { | 34 | const { |
35 | field, | 35 | field, |
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js index 5a9571d27..78d6aae8b 100644 --- a/src/components/ui/SearchInput.js +++ b/src/components/ui/SearchInput.js | |||
@@ -2,7 +2,6 @@ import React, { Component } from 'react'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import classnames from 'classnames'; | 4 | import classnames from 'classnames'; |
5 | import uuidv1 from 'uuid/v1'; | ||
6 | import { debounce } from 'lodash'; | 5 | import { debounce } from 'lodash'; |
7 | 6 | ||
8 | export default @observer class SearchInput extends Component { | 7 | export default @observer class SearchInput extends Component { |
@@ -22,7 +21,7 @@ export default @observer class SearchInput extends Component { | |||
22 | value: '', | 21 | value: '', |
23 | placeholder: '', | 22 | placeholder: '', |
24 | className: '', | 23 | className: '', |
25 | name: uuidv1(), | 24 | name: 'searchInput', |
26 | throttle: false, | 25 | throttle: false, |
27 | throttleDelay: 250, | 26 | throttleDelay: 250, |
28 | onChange: () => null, | 27 | onChange: () => null, |
@@ -30,6 +29,8 @@ export default @observer class SearchInput extends Component { | |||
30 | autoFocus: false, | 29 | autoFocus: false, |
31 | } | 30 | } |
32 | 31 | ||
32 | input = null; | ||
33 | |||
33 | constructor(props) { | 34 | constructor(props) { |
34 | super(props); | 35 | super(props); |
35 | 36 | ||
@@ -74,8 +75,6 @@ export default @observer class SearchInput extends Component { | |||
74 | onReset(); | 75 | onReset(); |
75 | } | 76 | } |
76 | 77 | ||
77 | input = null; | ||
78 | |||
79 | render() { | 78 | render() { |
80 | const { className, name, placeholder } = this.props; | 79 | const { className, name, placeholder } = this.props; |
81 | const { value } = this.state; | 80 | const { value } = this.state; |
@@ -90,15 +89,17 @@ export default @observer class SearchInput extends Component { | |||
90 | <label | 89 | <label |
91 | htmlFor={name} | 90 | htmlFor={name} |
92 | className="mdi mdi-magnify" | 91 | className="mdi mdi-magnify" |
93 | /> | 92 | > |
94 | <input | 93 | <input |
95 | name={name} | 94 | name={name} |
96 | type="text" | 95 | id={name} |
97 | placeholder={placeholder} | 96 | type="text" |
98 | value={value} | 97 | placeholder={placeholder} |
99 | onChange={e => this.onChange(e)} | 98 | value={value} |
100 | ref={(ref) => { this.input = ref; }} | 99 | onChange={e => this.onChange(e)} |
101 | /> | 100 | ref={(ref) => { this.input = ref; }} |
101 | /> | ||
102 | </label> | ||
102 | {value.length > 0 && ( | 103 | {value.length > 0 && ( |
103 | <span | 104 | <span |
104 | className="mdi mdi-close-circle-outline" | 105 | className="mdi mdi-close-circle-outline" |
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js index abcad417e..da52243ca 100644 --- a/src/components/ui/Select.js +++ b/src/components/ui/Select.js | |||
@@ -9,12 +9,13 @@ export default @observer class Select extends Component { | |||
9 | field: PropTypes.instanceOf(Field).isRequired, | 9 | field: PropTypes.instanceOf(Field).isRequired, |
10 | className: PropTypes.string, | 10 | className: PropTypes.string, |
11 | showLabel: PropTypes.bool, | 11 | showLabel: PropTypes.bool, |
12 | disabled: PropTypes.bool, | ||
12 | }; | 13 | }; |
13 | 14 | ||
14 | static defaultProps = { | 15 | static defaultProps = { |
15 | className: null, | 16 | className: null, |
16 | focus: false, | ||
17 | showLabel: true, | 17 | showLabel: true, |
18 | disabled: false, | ||
18 | }; | 19 | }; |
19 | 20 | ||
20 | render() { | 21 | render() { |
@@ -22,6 +23,7 @@ export default @observer class Select extends Component { | |||
22 | field, | 23 | field, |
23 | className, | 24 | className, |
24 | showLabel, | 25 | showLabel, |
26 | disabled, | ||
25 | } = this.props; | 27 | } = this.props; |
26 | 28 | ||
27 | return ( | 29 | return ( |
@@ -29,6 +31,7 @@ export default @observer class Select extends Component { | |||
29 | className={classnames({ | 31 | className={classnames({ |
30 | 'franz-form__field': true, | 32 | 'franz-form__field': true, |
31 | 'has-error': field.error, | 33 | 'has-error': field.error, |
34 | 'is-disabled': disabled, | ||
32 | [`${className}`]: className, | 35 | [`${className}`]: className, |
33 | })} | 36 | })} |
34 | > | 37 | > |
@@ -45,12 +48,13 @@ export default @observer class Select extends Component { | |||
45 | id={field.id} | 48 | id={field.id} |
46 | defaultValue={field.value} | 49 | defaultValue={field.value} |
47 | className="franz-form__select" | 50 | className="franz-form__select" |
51 | disabled={field.disabled || disabled} | ||
48 | > | 52 | > |
49 | {field.options.map(type => ( | 53 | {field.options.map(type => ( |
50 | <option | 54 | <option |
51 | key={type.value} | 55 | key={type.value} |
52 | value={type.value} | 56 | value={type.value} |
53 | // selected={field.value === } | 57 | disabled={type.disabled} |
54 | > | 58 | > |
55 | {type.label} | 59 | {type.label} |
56 | </option> | 60 | </option> |
diff --git a/src/components/ui/StatusBarTargetUrl.js b/src/components/ui/StatusBarTargetUrl.js index 4285a343c..6fc50fe5c 100644 --- a/src/components/ui/StatusBarTargetUrl.js +++ b/src/components/ui/StatusBarTargetUrl.js | |||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import classnames from 'classnames'; | 4 | import classnames from 'classnames'; |
5 | 5 | ||
6 | import Appear from '../ui/effects/Appear'; | 6 | import Appear from './effects/Appear'; |
7 | 7 | ||
8 | export default @observer class StatusBarTargetUrl extends Component { | 8 | export default @observer class StatusBarTargetUrl extends Component { |
9 | static propTypes = { | 9 | static propTypes = { |
@@ -13,7 +13,6 @@ export default @observer class StatusBarTargetUrl extends Component { | |||
13 | 13 | ||
14 | static defaultProps = { | 14 | static defaultProps = { |
15 | className: '', | 15 | className: '', |
16 | position: 'bottom', | ||
17 | text: '', | 16 | text: '', |
18 | }; | 17 | }; |
19 | 18 | ||
diff --git a/src/components/ui/Tabs/TabItem.js b/src/components/ui/Tabs/TabItem.js index 9ff9f009e..16881a7f7 100644 --- a/src/components/ui/Tabs/TabItem.js +++ b/src/components/ui/Tabs/TabItem.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component, Fragment } from 'react'; |
2 | 2 | ||
3 | import { oneOrManyChildElements } from '../../../prop-types'; | 3 | import { oneOrManyChildElements } from '../../../prop-types'; |
4 | 4 | ||
@@ -11,7 +11,7 @@ export default class TabItem extends Component { | |||
11 | const { children } = this.props; | 11 | const { children } = this.props; |
12 | 12 | ||
13 | return ( | 13 | return ( |
14 | <div>{children}</div> | 14 | <Fragment>{children}</Fragment> |
15 | ); | 15 | ); |
16 | } | 16 | } |
17 | } | 17 | } |
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js new file mode 100644 index 000000000..3a3dbbe49 --- /dev/null +++ b/src/components/ui/WebviewLoader/index.js | |||
@@ -0,0 +1,25 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | |||
6 | import FullscreenLoader from '../FullscreenLoader'; | ||
7 | |||
8 | import styles from './styles'; | ||
9 | |||
10 | export default @observer @injectSheet(styles) class WebviewLoader extends Component { | ||
11 | static propTypes = { | ||
12 | name: PropTypes.string.isRequired, | ||
13 | classes: PropTypes.object.isRequired, | ||
14 | } | ||
15 | |||
16 | render() { | ||
17 | const { classes, name } = this.props; | ||
18 | return ( | ||
19 | <FullscreenLoader | ||
20 | className={classes.component} | ||
21 | title={`Loading ${name}`} | ||
22 | /> | ||
23 | ); | ||
24 | } | ||
25 | } | ||
diff --git a/src/components/ui/WebviewLoader/styles.js b/src/components/ui/WebviewLoader/styles.js new file mode 100644 index 000000000..dbd75db8a --- /dev/null +++ b/src/components/ui/WebviewLoader/styles.js | |||
@@ -0,0 +1,9 @@ | |||
1 | export default theme => ({ | ||
2 | component: { | ||
3 | background: theme.colorWebviewLoaderBackground, | ||
4 | padding: 20, | ||
5 | width: 'auto', | ||
6 | margin: [0, 'auto'], | ||
7 | borderRadius: 6, | ||
8 | }, | ||
9 | }); | ||
diff --git a/src/components/util/ErrorBoundary/index.js b/src/components/util/ErrorBoundary/index.js new file mode 100644 index 000000000..5db0db226 --- /dev/null +++ b/src/components/util/ErrorBoundary/index.js | |||
@@ -0,0 +1,60 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import injectSheet from 'react-jss'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import Button from '../../ui/Button'; | ||
7 | |||
8 | import styles from './styles'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | headline: { | ||
12 | id: 'app.errorHandler.headline', | ||
13 | defaultMessage: '!!!Something went wrong.', | ||
14 | }, | ||
15 | action: { | ||
16 | id: 'app.errorHandler.action', | ||
17 | defaultMessage: '!!!Reload', | ||
18 | }, | ||
19 | }); | ||
20 | |||
21 | export default @injectSheet(styles) class ErrorBoundary extends Component { | ||
22 | state = { | ||
23 | hasError: false, | ||
24 | } | ||
25 | |||
26 | static propTypes = { | ||
27 | classes: PropTypes.object.isRequired, | ||
28 | children: PropTypes.node.isRequired, | ||
29 | } | ||
30 | |||
31 | static contextTypes = { | ||
32 | intl: intlShape, | ||
33 | }; | ||
34 | |||
35 | componentDidCatch() { | ||
36 | this.setState({ hasError: true }); | ||
37 | } | ||
38 | |||
39 | render() { | ||
40 | const { classes } = this.props; | ||
41 | const { intl } = this.context; | ||
42 | |||
43 | if (this.state.hasError) { | ||
44 | return ( | ||
45 | <div className={classes.component}> | ||
46 | <h1 className={classes.title}> | ||
47 | {intl.formatMessage(messages.headline)} | ||
48 | </h1> | ||
49 | <Button | ||
50 | label={intl.formatMessage(messages.action)} | ||
51 | buttonType="inverted" | ||
52 | onClick={() => window.location.reload()} | ||
53 | /> | ||
54 | </div> | ||
55 | ); | ||
56 | } | ||
57 | |||
58 | return this.props.children; | ||
59 | } | ||
60 | } | ||
diff --git a/src/components/util/ErrorBoundary/styles.js b/src/components/util/ErrorBoundary/styles.js new file mode 100644 index 000000000..0960546ff --- /dev/null +++ b/src/components/util/ErrorBoundary/styles.js | |||
@@ -0,0 +1,13 @@ | |||
1 | export default theme => ({ | ||
2 | component: { | ||
3 | display: 'flex', | ||
4 | width: '100%', | ||
5 | alignItems: 'center', | ||
6 | justifyContent: 'center', | ||
7 | flexDirection: 'column', | ||
8 | }, | ||
9 | title: { | ||
10 | fontSize: 20, | ||
11 | color: theme.colorText, | ||
12 | }, | ||
13 | }); | ||