diff options
author | 2018-02-12 14:16:33 +0100 | |
---|---|---|
committer | 2018-02-12 14:16:33 +0100 | |
commit | 7a1872e64262a76f17ac231a47fd8f57cd5a13ac (patch) | |
tree | 29ed8aa4dfcb9b179f4fe47c30fb33cf78c2e2ce /src/components | |
parent | Merge pull request #668 from meetfranz/fix/import-screen-toggles (diff) | |
parent | feat(App) Feature Invite Friends in Settings (diff) | |
download | ferdium-app-7a1872e64262a76f17ac231a47fd8f57cd5a13ac.tar.gz ferdium-app-7a1872e64262a76f17ac231a47fd8f57cd5a13ac.tar.zst ferdium-app-7a1872e64262a76f17ac231a47fd8f57cd5a13ac.zip |
Merge pull request #654 from meetfranz/feature/invite-button
Invite Button in Settings
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/auth/Invite.js | 121 | ||||
-rw-r--r-- | src/components/settings/navigation/SettingsNavigation.js | 11 | ||||
-rw-r--r-- | src/components/settings/services/ServiceError.js | 2 |
3 files changed, 109 insertions, 25 deletions
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js index c6dca3a65..f1c16986b 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.js | |||
@@ -3,13 +3,20 @@ import PropTypes from 'prop-types'; | |||
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import { Link } from 'react-router'; | 5 | import { Link } from 'react-router'; |
6 | import classnames from 'classnames'; | ||
6 | 7 | ||
8 | import Infobox from '../ui/Infobox'; | ||
9 | import Appear from '../ui/effects/Appear'; | ||
7 | import Form from '../../lib/Form'; | 10 | import Form from '../../lib/Form'; |
8 | import { email } from '../../helpers/validation-helpers'; | 11 | import { email } from '../../helpers/validation-helpers'; |
9 | import Input from '../ui/Input'; | 12 | import Input from '../ui/Input'; |
10 | import Button from '../ui/Button'; | 13 | import Button from '../ui/Button'; |
11 | 14 | ||
12 | const messages = defineMessages({ | 15 | const messages = defineMessages({ |
16 | settingsHeadline: { | ||
17 | id: 'settings.invite.headline', | ||
18 | defaultMessage: '!!!Invite Friends', | ||
19 | }, | ||
13 | headline: { | 20 | headline: { |
14 | id: 'invite.headline.friends', | 21 | id: 'invite.headline.friends', |
15 | defaultMessage: '!!!Invite 3 of your friends or colleagues', | 22 | defaultMessage: '!!!Invite 3 of your friends or colleagues', |
@@ -30,41 +37,77 @@ const messages = defineMessages({ | |||
30 | id: 'invite.skip.label', | 37 | id: 'invite.skip.label', |
31 | defaultMessage: '!!!I want to do this later', | 38 | defaultMessage: '!!!I want to do this later', |
32 | }, | 39 | }, |
40 | inviteSuccessInfo: { | ||
41 | id: 'invite.successInfo', | ||
42 | defaultMessage: '!!!Invitations sent successfully', | ||
43 | }, | ||
33 | }); | 44 | }); |
34 | 45 | ||
35 | @observer | 46 | @observer |
36 | export default class Invite extends Component { | 47 | export default class Invite extends Component { |
37 | static propTypes = { | 48 | static propTypes = { |
38 | onSubmit: PropTypes.func.isRequired, | 49 | onSubmit: PropTypes.func.isRequired, |
50 | embed: PropTypes.bool, | ||
51 | isInviteSuccessful: PropTypes.bool, | ||
52 | isLoadingInvite: PropTypes.bool, | ||
53 | }; | ||
54 | |||
55 | static defaultProps = { | ||
56 | embed: false, | ||
57 | isInviteSuccessful: false, | ||
58 | isLoadingInvite: false, | ||
39 | }; | 59 | }; |
40 | 60 | ||
41 | static contextTypes = { | 61 | static contextTypes = { |
42 | intl: intlShape, | 62 | intl: intlShape, |
43 | }; | 63 | }; |
44 | 64 | ||
45 | form = new Form({ | 65 | state = { showSuccessInfo: false }; |
46 | fields: { | 66 | |
47 | invite: [...Array(3).fill({ | 67 | componentWillMount() { |
48 | fields: { | 68 | const handlers = { |
49 | name: { | 69 | onChange: () => { |
50 | label: this.context.intl.formatMessage(messages.nameLabel), | 70 | this.setState({ showSuccessInfo: false }); |
51 | placeholder: this.context.intl.formatMessage(messages.nameLabel), | 71 | }, |
52 | }, | 72 | }; |
53 | email: { | 73 | |
54 | label: this.context.intl.formatMessage(messages.emailLabel), | 74 | this.form = new Form({ |
55 | placeholder: this.context.intl.formatMessage(messages.emailLabel), | 75 | fields: { |
56 | validators: [email], | 76 | invite: [...Array(3).fill({ |
77 | fields: { | ||
78 | name: { | ||
79 | label: this.context.intl.formatMessage(messages.nameLabel), | ||
80 | placeholder: this.context.intl.formatMessage(messages.nameLabel), | ||
81 | handlers, | ||
82 | // related: ['invite.0.email'], // path accepted but does not work | ||
83 | }, | ||
84 | email: { | ||
85 | label: this.context.intl.formatMessage(messages.emailLabel), | ||
86 | placeholder: this.context.intl.formatMessage(messages.emailLabel), | ||
87 | handlers, | ||
88 | validators: [email], | ||
89 | }, | ||
57 | }, | 90 | }, |
58 | }, | 91 | })], |
59 | })], | 92 | }, |
60 | }, | 93 | }, this.context.intl); |
61 | }, this.context.intl); | 94 | } |
95 | |||
96 | componentDidMount() { | ||
97 | document.querySelector('input:first-child').focus(); | ||
98 | } | ||
62 | 99 | ||
63 | submit(e) { | 100 | submit(e) { |
64 | e.preventDefault(); | 101 | e.preventDefault(); |
102 | |||
65 | this.form.submit({ | 103 | this.form.submit({ |
66 | onSuccess: (form) => { | 104 | onSuccess: (form) => { |
67 | this.props.onSubmit({ invites: form.values().invite }); | 105 | this.props.onSubmit({ invites: form.values().invite }); |
106 | |||
107 | this.form.clear(); | ||
108 | // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( | ||
109 | document.querySelector('input:first-child').focus(); | ||
110 | this.setState({ showSuccessInfo: true }); | ||
68 | }, | 111 | }, |
69 | onError: () => {}, | 112 | onError: () => {}, |
70 | }); | 113 | }); |
@@ -73,20 +116,38 @@ export default class Invite extends Component { | |||
73 | render() { | 116 | render() { |
74 | const { form } = this; | 117 | const { form } = this; |
75 | const { intl } = this.context; | 118 | const { intl } = this.context; |
119 | const { embed, isInviteSuccessful, isLoadingInvite } = this.props; | ||
76 | 120 | ||
77 | const atLeastOneEmailAddress = form.$('invite') | 121 | const atLeastOneEmailAddress = form.$('invite') |
78 | .map(invite => invite.$('email').value) | 122 | .map(invite => invite.$('email').value) |
79 | .some(emailValue => emailValue.trim() !== ''); | 123 | .some(emailValue => emailValue.trim() !== ''); |
80 | 124 | ||
81 | return ( | 125 | const sendButtonClassName = classnames({ |
82 | <div className="auth__container auth__container--signup"> | 126 | auth__button: true, |
127 | 'invite__embed--button': embed, | ||
128 | }); | ||
129 | |||
130 | const renderForm = ( | ||
131 | <div> | ||
132 | {this.state.showSuccessInfo && isInviteSuccessful && ( | ||
133 | <Appear> | ||
134 | <Infobox | ||
135 | type="success" | ||
136 | icon="checkbox-marked-circle-outline" | ||
137 | dismissable | ||
138 | > | ||
139 | {intl.formatMessage(messages.inviteSuccessInfo)} | ||
140 | </Infobox> | ||
141 | </Appear> | ||
142 | )} | ||
143 | |||
83 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> | 144 | <form className="franz-form auth__form" onSubmit={e => this.submit(e)}> |
84 | <img | 145 | {!embed && (<img |
85 | src="./assets/images/logo.svg" | 146 | src="./assets/images/logo.svg" |
86 | className="auth__logo" | 147 | className="auth__logo" |
87 | alt="" | 148 | alt="" |
88 | /> | 149 | />)} |
89 | <h1> | 150 | <h1 className={embed && 'invite__embed'}> |
90 | {intl.formatMessage(messages.headline)} | 151 | {intl.formatMessage(messages.headline)} |
91 | </h1> | 152 | </h1> |
92 | {form.$('invite').map(invite => ( | 153 | {form.$('invite').map(invite => ( |
@@ -99,18 +160,30 @@ export default class Invite extends Component { | |||
99 | ))} | 160 | ))} |
100 | <Button | 161 | <Button |
101 | type="submit" | 162 | type="submit" |
102 | className="auth__button" | 163 | className={sendButtonClassName} |
103 | disabled={!atLeastOneEmailAddress} | 164 | disabled={!atLeastOneEmailAddress} |
104 | label={intl.formatMessage(messages.submitButtonLabel)} | 165 | label={intl.formatMessage(messages.submitButtonLabel)} |
166 | loaded={!isLoadingInvite} | ||
105 | /> | 167 | /> |
106 | <Link | 168 | {!embed && (<Link |
107 | to="/" | 169 | to="/" |
108 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | 170 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" |
109 | > | 171 | > |
110 | {intl.formatMessage(messages.skipButtonLabel)} | 172 | {intl.formatMessage(messages.skipButtonLabel)} |
111 | </Link> | 173 | </Link>)} |
112 | </form> | 174 | </form> |
113 | </div> | 175 | </div> |
114 | ); | 176 | ); |
177 | |||
178 | return ( | ||
179 | <div className={!embed ? 'auth__container auth__container--signup' : 'settings__main'}> | ||
180 | {embed && ( | ||
181 | <div className="settings__header"> | ||
182 | <h1>{this.context.intl.formatMessage(messages.settingsHeadline)}</h1> | ||
183 | </div> | ||
184 | )} | ||
185 | {!embed ? <div>{renderForm}</div> : <div className="settings__body invite__form">{renderForm}</div>} | ||
186 | </div> | ||
187 | ); | ||
115 | } | 188 | } |
116 | } | 189 | } |
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index fea8d682d..66539f324 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js | |||
@@ -21,6 +21,10 @@ const messages = defineMessages({ | |||
21 | id: 'settings.navigation.settings', | 21 | id: 'settings.navigation.settings', |
22 | defaultMessage: '!!!Settings', | 22 | defaultMessage: '!!!Settings', |
23 | }, | 23 | }, |
24 | inviteFriends: { | ||
25 | id: 'settings.navigation.inviteFriends', | ||
26 | defaultMessage: '!!!Invite Friends', | ||
27 | }, | ||
24 | logout: { | 28 | logout: { |
25 | id: 'settings.navigation.logout', | 29 | id: 'settings.navigation.logout', |
26 | defaultMessage: '!!!Logout', | 30 | defaultMessage: '!!!Logout', |
@@ -70,6 +74,13 @@ export default class SettingsNavigation extends Component { | |||
70 | > | 74 | > |
71 | {intl.formatMessage(messages.settings)} | 75 | {intl.formatMessage(messages.settings)} |
72 | </Link> | 76 | </Link> |
77 | <Link | ||
78 | to="/settings/invite" | ||
79 | className="settings-navigation__link" | ||
80 | activeClassName="is-active" | ||
81 | > | ||
82 | {intl.formatMessage(messages.inviteFriends)} | ||
83 | </Link> | ||
73 | <span className="settings-navigation__expander" /> | 84 | <span className="settings-navigation__expander" /> |
74 | <Link | 85 | <Link |
75 | to="/auth/logout" | 86 | to="/auth/logout" |
diff --git a/src/components/settings/services/ServiceError.js b/src/components/settings/services/ServiceError.js index 923053296..1f1512927 100644 --- a/src/components/settings/services/ServiceError.js +++ b/src/components/settings/services/ServiceError.js | |||
@@ -26,7 +26,7 @@ const messages = defineMessages({ | |||
26 | }); | 26 | }); |
27 | 27 | ||
28 | @observer | 28 | @observer |
29 | export default class EditServiceForm extends Component { | 29 | export default class ServiceError extends Component { |
30 | static contextTypes = { | 30 | static contextTypes = { |
31 | intl: intlShape, | 31 | intl: intlShape, |
32 | }; | 32 | }; |