diff options
-rw-r--r-- | src/components/auth/Invite.js | 54 | ||||
-rw-r--r-- | src/components/settings/account/AccountDashboard.js | 29 | ||||
-rw-r--r-- | src/components/ui/Link.js | 5 | ||||
-rw-r--r-- | src/containers/auth/InviteScreen.js | 7 | ||||
-rw-r--r-- | src/containers/settings/AccountScreen.js | 1 | ||||
-rw-r--r-- | src/helpers/validation-helpers.js | 2 | ||||
-rw-r--r-- | src/i18n/locales/de.json | 1 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 2 | ||||
-rw-r--r-- | src/i18n/locales/es.json | 1 | ||||
-rw-r--r-- | src/i18n/locales/fr.json | 1 | ||||
-rw-r--r-- | src/i18n/locales/it.json | 1 | ||||
-rw-r--r-- | src/lib/Form.js | 5 | ||||
-rw-r--r-- | src/models/User.js | 2 | ||||
-rw-r--r-- | src/stores/UserStore.js | 8 | ||||
-rw-r--r-- | src/styles/invite.scss | 4 | ||||
-rw-r--r-- | src/styles/main.scss | 1 | ||||
-rw-r--r-- | src/styles/settings.scss | 9 |
17 files changed, 108 insertions, 25 deletions
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js index a420f98a2..1fe594d73 100644 --- a/src/components/auth/Invite.js +++ b/src/components/auth/Invite.js | |||
@@ -30,6 +30,10 @@ const messages = defineMessages({ | |||
30 | id: 'invite.skip.label', | 30 | id: 'invite.skip.label', |
31 | defaultMessage: '!!!I want to do this later', | 31 | defaultMessage: '!!!I want to do this later', |
32 | }, | 32 | }, |
33 | noEmailAddresses: { | ||
34 | id: 'invite.error.noEmails', | ||
35 | defaultMessage: '!!!At least one email address is required', | ||
36 | } | ||
33 | }); | 37 | }); |
34 | 38 | ||
35 | @observer | 39 | @observer |
@@ -45,17 +49,17 @@ export default class Invite extends Component { | |||
45 | form = new Form({ | 49 | form = new Form({ |
46 | fields: { | 50 | fields: { |
47 | invite: [...Array(3).fill({ | 51 | invite: [...Array(3).fill({ |
48 | name: { | 52 | fields: { |
49 | label: this.context.intl.formatMessage(messages.nameLabel), | 53 | name: { |
50 | // value: '', | 54 | label: this.context.intl.formatMessage(messages.nameLabel), |
51 | placeholder: this.context.intl.formatMessage(messages.nameLabel), | 55 | placeholder: this.context.intl.formatMessage(messages.nameLabel), |
52 | }, | 56 | }, |
53 | email: { | 57 | email: { |
54 | label: this.context.intl.formatMessage(messages.emailLabel), | 58 | label: this.context.intl.formatMessage(messages.emailLabel), |
55 | // value: '', | 59 | placeholder: this.context.intl.formatMessage(messages.emailLabel), |
56 | validators: [email], | 60 | validators: [email], |
57 | placeholder: this.context.intl.formatMessage(messages.emailLabel), | 61 | } |
58 | }, | 62 | } |
59 | })], | 63 | })], |
60 | }, | 64 | }, |
61 | }, this.context.intl); | 65 | }, this.context.intl); |
@@ -64,6 +68,21 @@ export default class Invite extends Component { | |||
64 | e.preventDefault(); | 68 | e.preventDefault(); |
65 | this.form.submit({ | 69 | this.form.submit({ |
66 | onSuccess: (form) => { | 70 | onSuccess: (form) => { |
71 | |||
72 | this.props.onSubmit({ | ||
73 | invites: form.values().invite, | ||
74 | from: this.props.from | ||
75 | }); | ||
76 | |||
77 | const atLeastOneEmailAddress = form.$('invite') | ||
78 | .map(invite => {return invite.$('email').value}) | ||
79 | .some(email => email.trim() !== '') | ||
80 | |||
81 | if (!atLeastOneEmailAddress) { | ||
82 | form.invalidate('no-email-addresses') | ||
83 | return | ||
84 | } | ||
85 | |||
67 | this.props.onSubmit({ invites: form.values().invite }); | 86 | this.props.onSubmit({ invites: form.values().invite }); |
68 | }, | 87 | }, |
69 | onError: () => {}, | 88 | onError: () => {}, |
@@ -73,6 +92,11 @@ export default class Invite extends Component { | |||
73 | render() { | 92 | render() { |
74 | const { form } = this; | 93 | const { form } = this; |
75 | const { intl } = this.context; | 94 | const { intl } = this.context; |
95 | const { from } = this.props; | ||
96 | |||
97 | const atLeastOneEmailAddress = form.$('invite') | ||
98 | .map(invite => {return invite.$('email').value}) | ||
99 | .some(email => email.trim() !== '') | ||
76 | 100 | ||
77 | return ( | 101 | return ( |
78 | <div className="auth__container auth__container--signup"> | 102 | <div className="auth__container auth__container--signup"> |
@@ -93,13 +117,19 @@ export default class Invite extends Component { | |||
93 | </div> | 117 | </div> |
94 | </div> | 118 | </div> |
95 | ))} | 119 | ))} |
120 | {form.error === 'no-email-addresses' && ( | ||
121 | <p className="franz-form__error invite-form__error"> | ||
122 | {intl.formatMessage(messages.noEmailAddresses)} | ||
123 | </p> | ||
124 | )} | ||
96 | <Button | 125 | <Button |
97 | type="submit" | 126 | type="submit" |
98 | className="auth__button" | 127 | className="auth__button" |
128 | disabled={!atLeastOneEmailAddress} | ||
99 | label={intl.formatMessage(messages.submitButtonLabel)} | 129 | label={intl.formatMessage(messages.submitButtonLabel)} |
100 | /> | 130 | /> |
101 | <Link | 131 | <Link |
102 | to="/" | 132 | to={ !!from ? from : '/'} |
103 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | 133 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" |
104 | > | 134 | > |
105 | {intl.formatMessage(messages.skipButtonLabel)} | 135 | {intl.formatMessage(messages.skipButtonLabel)} |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 43272fe96..d5f2e238c 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -48,6 +48,11 @@ const messages = defineMessages({ | |||
48 | id: 'settings.account.account.editButton', | 48 | id: 'settings.account.account.editButton', |
49 | defaultMessage: '!!!Edit Account', | 49 | defaultMessage: '!!!Edit Account', |
50 | }, | 50 | }, |
51 | accountInviteButton: { | ||
52 | id: "settings.account.account.inviteButton", | ||
53 | defaultMessage: '!!!Invite Friends', | ||
54 | }, | ||
55 | |||
51 | invoiceDownload: { | 56 | invoiceDownload: { |
52 | id: 'settings.account.invoiceDownload', | 57 | id: 'settings.account.invoiceDownload', |
53 | defaultMessage: '!!!Download', | 58 | defaultMessage: '!!!Download', |
@@ -113,6 +118,7 @@ export default class AccountDashboard extends Component { | |||
113 | deleteAccount, | 118 | deleteAccount, |
114 | isLoadingDeleteAccount, | 119 | isLoadingDeleteAccount, |
115 | isDeleteAccountSuccessful, | 120 | isDeleteAccountSuccessful, |
121 | pathname, | ||
116 | } = this.props; | 122 | } = this.props; |
117 | const { intl } = this.context; | 123 | const { intl } = this.context; |
118 | 124 | ||
@@ -174,10 +180,25 @@ export default class AccountDashboard extends Component { | |||
174 | <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> | 180 | <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> |
175 | )} | 181 | )} |
176 | </div> | 182 | </div> |
177 | <Link to="/settings/user/edit" className="button"> | 183 | <div className="grid"> |
178 | {intl.formatMessage(messages.accountEditButton)} | 184 | <div className="grid__row"> |
179 | </Link> | 185 | <Link to="/settings/user/edit" className="button account__edit-button"> |
180 | 186 | {intl.formatMessage(messages.accountEditButton)} | |
187 | </Link> | ||
188 | </div> | ||
189 | <div className="grid__row"> | ||
190 | <Link | ||
191 | to={{ | ||
192 | pathname: '/auth/signup/invite', | ||
193 | query: { from: pathname }, | ||
194 | // state: { "from": 'hi' } // is not being passed to route | ||
195 | }} | ||
196 | className="button account__invite-button"> | ||
197 | {intl.formatMessage(messages.accountInviteButton)} | ||
198 | </Link> | ||
199 | </div> | ||
200 | </div> | ||
201 | |||
181 | {user.emailValidated} | 202 | {user.emailValidated} |
182 | </div> | 203 | </div> |
183 | </div> | 204 | </div> |
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js index f5da921fa..bc3c2d8aa 100644 --- a/src/components/ui/Link.js +++ b/src/components/ui/Link.js | |||
@@ -62,7 +62,10 @@ Link.wrappedComponent.propTypes = { | |||
62 | oneOrManyChildElements, | 62 | oneOrManyChildElements, |
63 | PropTypes.string, | 63 | PropTypes.string, |
64 | ]).isRequired, | 64 | ]).isRequired, |
65 | to: PropTypes.string.isRequired, | 65 | to: PropTypes.oneOfType([ |
66 | PropTypes.string, | ||
67 | PropTypes.object | ||
68 | ]).isRequired, | ||
66 | className: PropTypes.string, | 69 | className: PropTypes.string, |
67 | activeClassName: PropTypes.string, | 70 | activeClassName: PropTypes.string, |
68 | strictFilter: PropTypes.bool, | 71 | strictFilter: PropTypes.bool, |
diff --git a/src/containers/auth/InviteScreen.js b/src/containers/auth/InviteScreen.js index 51971f436..a624e2245 100644 --- a/src/containers/auth/InviteScreen.js +++ b/src/containers/auth/InviteScreen.js | |||
@@ -11,10 +11,15 @@ export default class InviteScreen extends Component { | |||
11 | } | 11 | } |
12 | 12 | ||
13 | render() { | 13 | render() { |
14 | const { actions } = this.props; | 14 | const { |
15 | actions, | ||
16 | location | ||
17 | } = this.props; | ||
18 | |||
15 | return ( | 19 | return ( |
16 | <Invite | 20 | <Invite |
17 | onSubmit={actions.user.invite} | 21 | onSubmit={actions.user.invite} |
22 | from={location.query.from} | ||
18 | /> | 23 | /> |
19 | ); | 24 | ); |
20 | } | 25 | } |
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index c5c2982b0..b57530884 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -83,6 +83,7 @@ export default class AccountScreen extends Component { | |||
83 | deleteAccount={userActions.delete} | 83 | deleteAccount={userActions.delete} |
84 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} | 84 | isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} |
85 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} | 85 | isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} |
86 | pathname={this.props.location.pathname} | ||
86 | /> | 87 | /> |
87 | ); | 88 | ); |
88 | } | 89 | } |
diff --git a/src/helpers/validation-helpers.js b/src/helpers/validation-helpers.js index eeb12cab7..a8a242d54 100644 --- a/src/helpers/validation-helpers.js +++ b/src/helpers/validation-helpers.js | |||
@@ -13,7 +13,7 @@ export function email({ field }) { | |||
13 | isValid = true; | 13 | isValid = true; |
14 | } | 14 | } |
15 | 15 | ||
16 | return [isValid, `${field.label} is not a valid email address`]; | 16 | return [isValid, `${field.label} not valid`]; |
17 | } | 17 | } |
18 | 18 | ||
19 | export function url({ field }) { | 19 | export function url({ field }) { |
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 1ea0554ba..180db3018 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "services.getStarted" : "Loslegen", | 45 | "services.getStarted" : "Loslegen", |
46 | "services.welcome" : "Willkommen bei Franz.", | 46 | "services.welcome" : "Willkommen bei Franz.", |
47 | "settings.account.account.editButton" : "Konto bearbeiten", | 47 | "settings.account.account.editButton" : "Konto bearbeiten", |
48 | "settings.account.account.inviteButton" : "Freunde einladen", | ||
48 | "settings.account.accountType.basic" : "Basis Konto", | 49 | "settings.account.accountType.basic" : "Basis Konto", |
49 | "settings.account.accountType.premium" : "Premium-Supporter Konto", | 50 | "settings.account.accountType.premium" : "Premium-Supporter Konto", |
50 | "settings.account.buttonSave" : "Profil aktualisieren", | 51 | "settings.account.buttonSave" : "Profil aktualisieren", |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 63d7ce60b..c9e9b860b 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "invite.name.label": "Name", | 45 | "invite.name.label": "Name", |
46 | "invite.email.label": "Email address", | 46 | "invite.email.label": "Email address", |
47 | "invite.skip.label": "I want to do this later", | 47 | "invite.skip.label": "I want to do this later", |
48 | "invite.error.noEmails": "At least one email address is required", | ||
48 | "subscription.submit.label": "I want to support the development of Franz", | 49 | "subscription.submit.label": "I want to support the development of Franz", |
49 | "subscription.paymentSessionError": "Could not initialize payment form", | 50 | "subscription.paymentSessionError": "Could not initialize payment form", |
50 | "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes", | 51 | "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes", |
@@ -76,6 +77,7 @@ | |||
76 | "settings.account.accountType.basic": "Basic Account", | 77 | "settings.account.accountType.basic": "Basic Account", |
77 | "settings.account.accountType.premium": "Premium Supporter Account", | 78 | "settings.account.accountType.premium": "Premium Supporter Account", |
78 | "settings.account.account.editButton": "Edit account", | 79 | "settings.account.account.editButton": "Edit account", |
80 | "settings.account.account.inviteButton" : "Invite friends", | ||
79 | "settings.account.invoiceDownload": "Download", | 81 | "settings.account.invoiceDownload": "Download", |
80 | "settings.account.userInfoRequestFailed": "Could not load user information", | 82 | "settings.account.userInfoRequestFailed": "Could not load user information", |
81 | "settings.account.tryReloadUserInfoRequest": "Try again", | 83 | "settings.account.tryReloadUserInfoRequest": "Try again", |
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index c4f598bd1..dddd1bf1f 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "services.getStarted" : "Primeros pasos", | 45 | "services.getStarted" : "Primeros pasos", |
46 | "services.welcome" : "Bienvenido a Franz", | 46 | "services.welcome" : "Bienvenido a Franz", |
47 | "settings.account.account.editButton" : "Editar cuenta", | 47 | "settings.account.account.editButton" : "Editar cuenta", |
48 | "settings.account.account.inviteButton" : "Invitar amigos", | ||
48 | "settings.account.accountType.basic" : "Cuenta Básica", | 49 | "settings.account.accountType.basic" : "Cuenta Básica", |
49 | "settings.account.accountType.premium" : "Cuenta Colaborador Premium", | 50 | "settings.account.accountType.premium" : "Cuenta Colaborador Premium", |
50 | "settings.account.buttonSave" : "Actualizar perfil", | 51 | "settings.account.buttonSave" : "Actualizar perfil", |
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 4a06975bb..a717577be 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "services.getStarted" : "Commencer", | 45 | "services.getStarted" : "Commencer", |
46 | "services.welcome" : "Bienvenue dans Franz", | 46 | "services.welcome" : "Bienvenue dans Franz", |
47 | "settings.account.account.editButton" : "Modifier le compte", | 47 | "settings.account.account.editButton" : "Modifier le compte", |
48 | "settings.account.account.inviteButton" : "Inviter des amis", | ||
48 | "settings.account.accountType.basic" : "Compte de base", | 49 | "settings.account.accountType.basic" : "Compte de base", |
49 | "settings.account.accountType.premium" : "Compte supporteur premium", | 50 | "settings.account.accountType.premium" : "Compte supporteur premium", |
50 | "settings.account.buttonSave" : "Mettre à jour le profil", | 51 | "settings.account.buttonSave" : "Mettre à jour le profil", |
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 1d2485b19..5b89b3a12 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json | |||
@@ -45,6 +45,7 @@ | |||
45 | "services.getStarted" : "Iniziamo", | 45 | "services.getStarted" : "Iniziamo", |
46 | "services.welcome" : "Benvenuto in Franz", | 46 | "services.welcome" : "Benvenuto in Franz", |
47 | "settings.account.account.editButton" : "Modifica account", | 47 | "settings.account.account.editButton" : "Modifica account", |
48 | "settings.account.account.inviteButton" : "Invitare amicos", | ||
48 | "settings.account.accountType.basic" : "Account Basic", | 49 | "settings.account.accountType.basic" : "Account Basic", |
49 | "settings.account.accountType.premium" : "Premium Supporter Account", | 50 | "settings.account.accountType.premium" : "Premium Supporter Account", |
50 | "settings.account.buttonSave" : "Aggiorna profilo", | 51 | "settings.account.buttonSave" : "Aggiorna profilo", |
diff --git a/src/lib/Form.js b/src/lib/Form.js index a22699b45..9b8321948 100644 --- a/src/lib/Form.js +++ b/src/lib/Form.js | |||
@@ -21,8 +21,9 @@ export default class DefaultForm extends Form { | |||
21 | 21 | ||
22 | options() { | 22 | options() { |
23 | return { | 23 | return { |
24 | validateOnInit: false, | 24 | validateOnInit: false, // default: true |
25 | // validateOnBlur: true, | 25 | // validateOnBlur: true, // default: true |
26 | // validateOnChange: true // default: false | ||
26 | // // validationDebounceWait: { | 27 | // // validationDebounceWait: { |
27 | // // trailing: true, | 28 | // // trailing: true, |
28 | // // }, | 29 | // // }, |
diff --git a/src/models/User.js b/src/models/User.js index 2e5df4795..d2a455e20 100644 --- a/src/models/User.js +++ b/src/models/User.js | |||
@@ -10,7 +10,7 @@ export default class User { | |||
10 | @observable emailIsConfirmed = true; // better assume it's confirmed to avoid noise | 10 | @observable emailIsConfirmed = true; // better assume it's confirmed to avoid noise |
11 | @observable subscription = {}; | 11 | @observable subscription = {}; |
12 | @observable isSubscriptionOwner = false; | 12 | @observable isSubscriptionOwner = false; |
13 | @observable isPremium = false; | 13 | @observable isPremium = true; |
14 | @observable beta = false; | 14 | @observable beta = false; |
15 | @observable donor = {}; | 15 | @observable donor = {}; |
16 | @observable isDonor = false; | 16 | @observable isDonor = false; |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 09000dcdb..476c1f040 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -160,13 +160,13 @@ export default class UserStore extends Store { | |||
160 | gaEvent('User', 'retrievePassword'); | 160 | gaEvent('User', 'retrievePassword'); |
161 | } | 161 | } |
162 | 162 | ||
163 | @action _invite({ invites }) { | 163 | @action _invite({ invites, from }) { |
164 | const data = invites.filter(invite => invite.email !== ''); | 164 | const data = invites.filter(invite => invite.email !== ''); |
165 | 165 | ||
166 | this.inviteRequest.execute(data); | 166 | this.inviteRequest.execute(data); |
167 | 167 | ||
168 | // we do not wait for a server response before redirecting the user | 168 | // we do not wait for a server response before redirecting the user |
169 | this.stores.router.push('/'); | 169 | this.stores.router.push(!!from ? from : '/'); |
170 | 170 | ||
171 | gaEvent('User', 'inviteUsers'); | 171 | gaEvent('User', 'inviteUsers'); |
172 | } | 172 | } |
@@ -237,7 +237,9 @@ export default class UserStore extends Store { | |||
237 | && currentRoute.includes(this.BASE_ROUTE) | 237 | && currentRoute.includes(this.BASE_ROUTE) |
238 | && (this.hasCompletedSignup | 238 | && (this.hasCompletedSignup |
239 | || this.hasCompletedSignup === null)) { | 239 | || this.hasCompletedSignup === null)) { |
240 | this.stores.router.push('/'); | 240 | if (!isDevMode) { |
241 | this.stores.router.push('/'); | ||
242 | } | ||
241 | } | 243 | } |
242 | }; | 244 | }; |
243 | 245 | ||
diff --git a/src/styles/invite.scss b/src/styles/invite.scss new file mode 100644 index 000000000..345095da8 --- /dev/null +++ b/src/styles/invite.scss | |||
@@ -0,0 +1,4 @@ | |||
1 | .invite-form__error { | ||
2 | text-align: center; | ||
3 | margin-bottom: 20px !important; /* otherwise overridden by rule: p:last-of-type */ | ||
4 | } | ||
diff --git a/src/styles/main.scss b/src/styles/main.scss index 261396f6f..20f1803e4 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss | |||
@@ -18,6 +18,7 @@ $mdi-font-path: '../node_modules/mdi/fonts'; | |||
18 | @import './type.scss'; | 18 | @import './type.scss'; |
19 | @import './welcome.scss'; | 19 | @import './welcome.scss'; |
20 | @import './auth.scss'; | 20 | @import './auth.scss'; |
21 | @import './invite.scss'; | ||
21 | @import './tooltip.scss'; | 22 | @import './tooltip.scss'; |
22 | @import './info-bar.scss'; | 23 | @import './info-bar.scss'; |
23 | @import './status-bar-target-url.scss'; | 24 | @import './status-bar-target-url.scss'; |
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 2182c9b5f..f436b1eda 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -282,6 +282,15 @@ | |||
282 | } | 282 | } |
283 | } | 283 | } |
284 | 284 | ||
285 | .account__edit-button { | ||
286 | width: 170px; | ||
287 | margin-bottom: 10px; | ||
288 | } | ||
289 | |||
290 | .account__invite-button { | ||
291 | width: 170px; | ||
292 | } | ||
293 | |||
285 | .account__subscription { | 294 | .account__subscription { |
286 | display: flex; | 295 | display: flex; |
287 | align-items: center; | 296 | align-items: center; |