diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-04-16 11:53:30 +0200 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-04-16 11:53:30 +0200 |
commit | 03950e3f8a43691bdb1371625b860b29f40a6e60 (patch) | |
tree | 4765ba98e1e3dade746a7d0216b6bde6c9d1cce6 /src/components | |
parent | Merge branch 'feature/announcements' into develop (diff) | |
parent | Merge pull request #1393 from meetfranz/chore/streamline-dashboard (diff) | |
download | ferdium-app-03950e3f8a43691bdb1371625b860b29f40a6e60.tar.gz ferdium-app-03950e3f8a43691bdb1371625b860b29f40a6e60.tar.zst ferdium-app-03950e3f8a43691bdb1371625b860b29f40a6e60.zip |
Merge branch 'develop' of https://github.com/meetfranz/franz into develop
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/settings/account/AccountDashboard.js | 126 | ||||
-rw-r--r-- | src/components/settings/navigation/SettingsNavigation.js | 17 | ||||
-rw-r--r-- | src/components/settings/team/TeamDashboard.js | 152 |
3 files changed, 216 insertions, 79 deletions
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 181b95c8c..3f6964b6b 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -3,12 +3,11 @@ 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'; |
5 | import ReactTooltip from 'react-tooltip'; | 5 | import ReactTooltip from 'react-tooltip'; |
6 | import moment from 'moment'; | 6 | import { ProBadge } from '@meetfranz/ui'; |
7 | 7 | ||
8 | import Loader from '../../ui/Loader'; | 8 | import Loader from '../../ui/Loader'; |
9 | import Button from '../../ui/Button'; | 9 | import Button from '../../ui/Button'; |
10 | import Infobox from '../../ui/Infobox'; | 10 | import Infobox from '../../ui/Infobox'; |
11 | import Link from '../../ui/Link'; | ||
12 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; | 11 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; |
13 | 12 | ||
14 | const messages = defineMessages({ | 13 | const messages = defineMessages({ |
@@ -24,10 +23,6 @@ const messages = defineMessages({ | |||
24 | id: 'settings.account.headlineUpgrade', | 23 | id: 'settings.account.headlineUpgrade', |
25 | defaultMessage: '!!!Upgrade your Account', | 24 | defaultMessage: '!!!Upgrade your Account', |
26 | }, | 25 | }, |
27 | headlineInvoices: { | ||
28 | id: 'settings.account.headlineInvoices', | ||
29 | defaultMessage: '!!Invoices', | ||
30 | }, | ||
31 | headlineDangerZone: { | 26 | headlineDangerZone: { |
32 | id: 'settings.account.headlineDangerZone', | 27 | id: 'settings.account.headlineDangerZone', |
33 | defaultMessage: '!!Danger Zone', | 28 | defaultMessage: '!!Danger Zone', |
@@ -48,6 +43,10 @@ const messages = defineMessages({ | |||
48 | id: 'settings.account.account.editButton', | 43 | id: 'settings.account.account.editButton', |
49 | defaultMessage: '!!!Edit Account', | 44 | defaultMessage: '!!!Edit Account', |
50 | }, | 45 | }, |
46 | invoicesButton: { | ||
47 | id: 'settings.account.headlineInvoices', | ||
48 | defaultMessage: '!!Invoices', | ||
49 | }, | ||
51 | invoiceDownload: { | 50 | invoiceDownload: { |
52 | id: 'settings.account.invoiceDownload', | 51 | id: 'settings.account.invoiceDownload', |
53 | defaultMessage: '!!!Download', | 52 | defaultMessage: '!!!Download', |
@@ -77,19 +76,17 @@ const messages = defineMessages({ | |||
77 | export default @observer class AccountDashboard extends Component { | 76 | export default @observer class AccountDashboard extends Component { |
78 | static propTypes = { | 77 | static propTypes = { |
79 | user: MobxPropTypes.observableObject.isRequired, | 78 | user: MobxPropTypes.observableObject.isRequired, |
80 | orders: MobxPropTypes.arrayOrObservableArray.isRequired, | ||
81 | isLoading: PropTypes.bool.isRequired, | 79 | isLoading: PropTypes.bool.isRequired, |
82 | isLoadingOrdersInfo: PropTypes.bool.isRequired, | ||
83 | isLoadingPlans: PropTypes.bool.isRequired, | 80 | isLoadingPlans: PropTypes.bool.isRequired, |
84 | isCreatingPaymentDashboardUrl: PropTypes.bool.isRequired, | ||
85 | userInfoRequestFailed: PropTypes.bool.isRequired, | 81 | userInfoRequestFailed: PropTypes.bool.isRequired, |
86 | retryUserInfoRequest: PropTypes.func.isRequired, | 82 | retryUserInfoRequest: PropTypes.func.isRequired, |
87 | openDashboard: PropTypes.func.isRequired, | ||
88 | openExternalUrl: PropTypes.func.isRequired, | ||
89 | onCloseSubscriptionWindow: PropTypes.func.isRequired, | 83 | onCloseSubscriptionWindow: PropTypes.func.isRequired, |
90 | deleteAccount: PropTypes.func.isRequired, | 84 | deleteAccount: PropTypes.func.isRequired, |
91 | isLoadingDeleteAccount: PropTypes.bool.isRequired, | 85 | isLoadingDeleteAccount: PropTypes.bool.isRequired, |
92 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, | 86 | isDeleteAccountSuccessful: PropTypes.bool.isRequired, |
87 | openEditAccount: PropTypes.func.isRequired, | ||
88 | openBilling: PropTypes.func.isRequired, | ||
89 | openInvoices: PropTypes.func.isRequired, | ||
93 | }; | 90 | }; |
94 | 91 | ||
95 | static contextTypes = { | 92 | static contextTypes = { |
@@ -99,12 +96,7 @@ export default @observer class AccountDashboard extends Component { | |||
99 | render() { | 96 | render() { |
100 | const { | 97 | const { |
101 | user, | 98 | user, |
102 | orders, | ||
103 | isLoading, | 99 | isLoading, |
104 | isCreatingPaymentDashboardUrl, | ||
105 | openDashboard, | ||
106 | openExternalUrl, | ||
107 | isLoadingOrdersInfo, | ||
108 | isLoadingPlans, | 100 | isLoadingPlans, |
109 | userInfoRequestFailed, | 101 | userInfoRequestFailed, |
110 | retryUserInfoRequest, | 102 | retryUserInfoRequest, |
@@ -112,6 +104,9 @@ export default @observer class AccountDashboard extends Component { | |||
112 | deleteAccount, | 104 | deleteAccount, |
113 | isLoadingDeleteAccount, | 105 | isLoadingDeleteAccount, |
114 | isDeleteAccountSuccessful, | 106 | isDeleteAccountSuccessful, |
107 | openEditAccount, | ||
108 | openBilling, | ||
109 | openInvoices, | ||
115 | } = this.props; | 110 | } = this.props; |
116 | const { intl } = this.context; | 111 | const { intl } = this.context; |
117 | 112 | ||
@@ -149,80 +144,53 @@ export default @observer class AccountDashboard extends Component { | |||
149 | src="./assets/images/logo.svg" | 144 | src="./assets/images/logo.svg" |
150 | alt="" | 145 | alt="" |
151 | /> | 146 | /> |
152 | {user.isPremium && ( | ||
153 | <span | ||
154 | className="account__avatar-premium emoji" | ||
155 | data-tip="Premium Supporter Account" | ||
156 | > | ||
157 | <img src="./assets/images/emoji/star.png" alt="" /> | ||
158 | </span> | ||
159 | )} | ||
160 | </div> | 147 | </div> |
161 | <div className="account__info"> | 148 | <div className="account__info"> |
162 | <h2> | 149 | <h2> |
163 | {`${user.firstname} ${user.lastname}`} | 150 | <span className="username">{`${user.firstname} ${user.lastname}`}</span> |
151 | {user.isPremium && ( | ||
152 | <> | ||
153 | {' '} | ||
154 | <ProBadge /> | ||
155 | <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> | ||
156 | </> | ||
157 | )} | ||
164 | </h2> | 158 | </h2> |
165 | {user.organization && `${user.organization}, `} | 159 | {user.organization && `${user.organization}, `} |
166 | {user.email} | 160 | {user.email} |
167 | <br /> | ||
168 | {user.isPremium && ( | 161 | {user.isPremium && ( |
169 | <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> | 162 | <div className="manage-user-links"> |
163 | <Button | ||
164 | label={intl.formatMessage(messages.accountEditButton)} | ||
165 | className="franz-form__button--inverted" | ||
166 | onClick={openEditAccount} | ||
167 | /> | ||
168 | {user.isSubscriptionOwner && ( | ||
169 | <> | ||
170 | <Button | ||
171 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} | ||
172 | className="franz-form__button--inverted" | ||
173 | onClick={openBilling} | ||
174 | /> | ||
175 | <Button | ||
176 | label={intl.formatMessage(messages.invoicesButton)} | ||
177 | className="franz-form__button--inverted" | ||
178 | onClick={openInvoices} | ||
179 | /> | ||
180 | </> | ||
181 | )} | ||
182 | </div> | ||
170 | )} | 183 | )} |
171 | </div> | 184 | </div> |
172 | <Link to="/settings/user/edit" className="button"> | 185 | {!user.isPremium && ( |
173 | {intl.formatMessage(messages.accountEditButton)} | 186 | <Button |
174 | </Link> | 187 | label={intl.formatMessage(messages.accountEditButton)} |
175 | {user.emailValidated} | 188 | className="franz-form__button--inverted" |
176 | </div> | 189 | onClick={openEditAccount} |
177 | </div> | 190 | /> |
178 | )} | ||
179 | |||
180 | {user.isSubscriptionOwner && ( | ||
181 | isLoadingOrdersInfo ? ( | ||
182 | <Loader /> | ||
183 | ) : ( | ||
184 | <div className="account franz-form"> | ||
185 | {orders.length > 0 && ( | ||
186 | <Fragment> | ||
187 | <div className="account__box"> | ||
188 | <h2>{intl.formatMessage(messages.headlineSubscription)}</h2> | ||
189 | <div className="account__subscription"> | ||
190 | {orders[0].name} | ||
191 | <span className="badge">{orders[0].price}</span> | ||
192 | <Button | ||
193 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} | ||
194 | className="account__subscription-button franz-form__button--inverted" | ||
195 | loaded={!isCreatingPaymentDashboardUrl} | ||
196 | onClick={() => openDashboard()} | ||
197 | /> | ||
198 | </div> | ||
199 | </div> | ||
200 | <div className="account__box"> | ||
201 | <h2>{intl.formatMessage(messages.headlineInvoices)}</h2> | ||
202 | <table className="invoices"> | ||
203 | <tbody> | ||
204 | {orders.map(order => ( | ||
205 | <tr key={order.id}> | ||
206 | <td className="invoices__date"> | ||
207 | {moment(order.date).format('DD.MM.YYYY')} | ||
208 | </td> | ||
209 | <td className="invoices__action"> | ||
210 | <button | ||
211 | type="button" | ||
212 | onClick={() => openExternalUrl(order.invoiceUrl)} | ||
213 | > | ||
214 | {intl.formatMessage(messages.invoiceDownload)} | ||
215 | </button> | ||
216 | </td> | ||
217 | </tr> | ||
218 | ))} | ||
219 | </tbody> | ||
220 | </table> | ||
221 | </div> | ||
222 | </Fragment> | ||
223 | )} | 191 | )} |
224 | </div> | 192 | </div> |
225 | ) | 193 | </div> |
226 | )} | 194 | )} |
227 | 195 | ||
228 | {!user.isPremium && ( | 196 | {!user.isPremium && ( |
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index 993b0a44a..df4b3b3b2 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js | |||
@@ -7,6 +7,7 @@ import { ProBadge } from '@meetfranz/ui'; | |||
7 | import Link from '../../ui/Link'; | 7 | import Link from '../../ui/Link'; |
8 | import { workspaceStore } from '../../../features/workspaces'; | 8 | import { workspaceStore } from '../../../features/workspaces'; |
9 | import UIStore from '../../../stores/UIStore'; | 9 | import UIStore from '../../../stores/UIStore'; |
10 | import UserStore from '../../../stores/UserStore'; | ||
10 | 11 | ||
11 | const messages = defineMessages({ | 12 | const messages = defineMessages({ |
12 | availableServices: { | 13 | availableServices: { |
@@ -25,6 +26,10 @@ const messages = defineMessages({ | |||
25 | id: 'settings.navigation.account', | 26 | id: 'settings.navigation.account', |
26 | defaultMessage: '!!!Account', | 27 | defaultMessage: '!!!Account', |
27 | }, | 28 | }, |
29 | team: { | ||
30 | id: 'settings.navigation.team', | ||
31 | defaultMessage: '!!!Manage Team', | ||
32 | }, | ||
28 | settings: { | 33 | settings: { |
29 | id: 'settings.navigation.settings', | 34 | id: 'settings.navigation.settings', |
30 | defaultMessage: '!!!Settings', | 35 | defaultMessage: '!!!Settings', |
@@ -43,6 +48,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp | |||
43 | static propTypes = { | 48 | static propTypes = { |
44 | stores: PropTypes.shape({ | 49 | stores: PropTypes.shape({ |
45 | ui: PropTypes.instanceOf(UIStore).isRequired, | 50 | ui: PropTypes.instanceOf(UIStore).isRequired, |
51 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
46 | }).isRequired, | 52 | }).isRequired, |
47 | serviceCount: PropTypes.number.isRequired, | 53 | serviceCount: PropTypes.number.isRequired, |
48 | workspaceCount: PropTypes.number.isRequired, | 54 | workspaceCount: PropTypes.number.isRequired, |
@@ -55,6 +61,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp | |||
55 | render() { | 61 | render() { |
56 | const { serviceCount, workspaceCount, stores } = this.props; | 62 | const { serviceCount, workspaceCount, stores } = this.props; |
57 | const { isDarkThemeActive } = stores.ui; | 63 | const { isDarkThemeActive } = stores.ui; |
64 | const { router, user } = stores; | ||
58 | const { intl } = this.context; | 65 | const { intl } = this.context; |
59 | 66 | ||
60 | return ( | 67 | return ( |
@@ -98,6 +105,16 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp | |||
98 | {intl.formatMessage(messages.account)} | 105 | {intl.formatMessage(messages.account)} |
99 | </Link> | 106 | </Link> |
100 | <Link | 107 | <Link |
108 | to="/settings/team" | ||
109 | className="settings-navigation__link" | ||
110 | activeClassName="is-active" | ||
111 | > | ||
112 | {intl.formatMessage(messages.team)} | ||
113 | {!user.data.isPremium && ( | ||
114 | <ProBadge inverted={!isDarkThemeActive && router.location.pathname === '/settings/team'} /> | ||
115 | )} | ||
116 | </Link> | ||
117 | <Link | ||
101 | to="/settings/app" | 118 | to="/settings/app" |
102 | className="settings-navigation__link" | 119 | className="settings-navigation__link" |
103 | activeClassName="is-active" | 120 | activeClassName="is-active" |
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js new file mode 100644 index 000000000..82c517fcb --- /dev/null +++ b/src/components/settings/team/TeamDashboard.js | |||
@@ -0,0 +1,152 @@ | |||
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 ReactTooltip from 'react-tooltip'; | ||
6 | import injectSheet from 'react-jss'; | ||
7 | |||
8 | import Loader from '../../ui/Loader'; | ||
9 | import Button from '../../ui/Button'; | ||
10 | import Infobox from '../../ui/Infobox'; | ||
11 | import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; | ||
12 | |||
13 | const messages = defineMessages({ | ||
14 | headline: { | ||
15 | id: 'settings.team.headline', | ||
16 | defaultMessage: '!!!Team', | ||
17 | }, | ||
18 | contentHeadline: { | ||
19 | id: 'settings.team.contentHeadline', | ||
20 | defaultMessage: '!!!Franz for Teams', | ||
21 | }, | ||
22 | intro: { | ||
23 | id: 'settings.team.intro', | ||
24 | defaultMessage: '!!!You and your team use Franz? You can now manage Premium subscriptions for as many colleagues, friends or family members as you want, all from within one account.', | ||
25 | }, | ||
26 | copy: { | ||
27 | id: 'settings.team.copy', | ||
28 | defaultMessage: '!!!Franz for Teams gives you the option to invite co-workers to your team by sending them email invitations and manage their subscriptions in your account’s preferences. Don’t waste time setting up subscriptions for every team member individually, forget about multiple invoices and different billing cycles - one team to rule them all!', | ||
29 | }, | ||
30 | manageButton: { | ||
31 | id: 'settings.team.manageAction', | ||
32 | defaultMessage: '!!!Manage your Team on meetfranz.com', | ||
33 | }, | ||
34 | upgradeButton: { | ||
35 | id: 'settings.team.upgradeAction', | ||
36 | defaultMessage: '!!!Upgrade your Account', | ||
37 | }, | ||
38 | }); | ||
39 | |||
40 | const styles = { | ||
41 | cta: { | ||
42 | margin: [40, 'auto'], | ||
43 | }, | ||
44 | container: { | ||
45 | display: 'flex', | ||
46 | flexDirection: 'column', | ||
47 | height: 'auto', | ||
48 | |||
49 | '@media(min-width: 800px)': { | ||
50 | flexDirection: 'row', | ||
51 | }, | ||
52 | }, | ||
53 | content: { | ||
54 | height: 'auto', | ||
55 | order: 1, | ||
56 | |||
57 | '@media(min-width: 800px)': { | ||
58 | order: 0, | ||
59 | }, | ||
60 | }, | ||
61 | image: { | ||
62 | display: 'block', | ||
63 | height: 150, | ||
64 | order: 0, | ||
65 | margin: [0, 'auto', 40, 'auto'], | ||
66 | |||
67 | '@media(min-width: 800px)': { | ||
68 | marginLeft: 40, | ||
69 | order: 1, | ||
70 | }, | ||
71 | }, | ||
72 | }; | ||
73 | |||
74 | |||
75 | export default @injectSheet(styles) @observer class TeamDashboard extends Component { | ||
76 | static propTypes = { | ||
77 | isLoading: PropTypes.bool.isRequired, | ||
78 | userInfoRequestFailed: PropTypes.bool.isRequired, | ||
79 | retryUserInfoRequest: PropTypes.func.isRequired, | ||
80 | openTeamManagement: PropTypes.func.isRequired, | ||
81 | classes: PropTypes.object.isRequired, | ||
82 | }; | ||
83 | |||
84 | static contextTypes = { | ||
85 | intl: intlShape, | ||
86 | }; | ||
87 | |||
88 | render() { | ||
89 | const { | ||
90 | isLoading, | ||
91 | userInfoRequestFailed, | ||
92 | retryUserInfoRequest, | ||
93 | openTeamManagement, | ||
94 | classes, | ||
95 | } = this.props; | ||
96 | const { intl } = this.context; | ||
97 | |||
98 | return ( | ||
99 | <div className="settings__main"> | ||
100 | <div className="settings__header"> | ||
101 | <span className="settings__header-item"> | ||
102 | {intl.formatMessage(messages.headline)} | ||
103 | </span> | ||
104 | </div> | ||
105 | <div className="settings__body"> | ||
106 | {isLoading && ( | ||
107 | <Loader /> | ||
108 | )} | ||
109 | |||
110 | {!isLoading && userInfoRequestFailed && ( | ||
111 | <Infobox | ||
112 | icon="alert" | ||
113 | type="danger" | ||
114 | ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} | ||
115 | ctaLoading={isLoading} | ||
116 | ctaOnClick={retryUserInfoRequest} | ||
117 | > | ||
118 | {intl.formatMessage(messages.userInfoRequestFailed)} | ||
119 | </Infobox> | ||
120 | )} | ||
121 | |||
122 | {!userInfoRequestFailed && ( | ||
123 | <> | ||
124 | {!isLoading && ( | ||
125 | <> | ||
126 | <PremiumFeatureContainer> | ||
127 | <> | ||
128 | <h1>{intl.formatMessage(messages.contentHeadline)}</h1> | ||
129 | <div className={classes.container}> | ||
130 | <div className={classes.content}> | ||
131 | <p>{intl.formatMessage(messages.intro)}</p> | ||
132 | <p>{intl.formatMessage(messages.copy)}</p> | ||
133 | </div> | ||
134 | <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" /> | ||
135 | </div> | ||
136 | <Button | ||
137 | label={intl.formatMessage(messages.manageButton)} | ||
138 | onClick={openTeamManagement} | ||
139 | className={classes.cta} | ||
140 | /> | ||
141 | </> | ||
142 | </PremiumFeatureContainer> | ||
143 | </> | ||
144 | )} | ||
145 | </> | ||
146 | )} | ||
147 | </div> | ||
148 | <ReactTooltip place="right" type="dark" effect="solid" /> | ||
149 | </div> | ||
150 | ); | ||
151 | } | ||
152 | } | ||