aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/auth
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-10-13 12:29:40 +0200
commit58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch)
tree1211600c2a5d3b5f81c435c6896618111a611720 /src/components/auth
downloadferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst
ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip
initial commit
Diffstat (limited to 'src/components/auth')
-rw-r--r--src/components/auth/AuthLayout.js88
-rw-r--r--src/components/auth/Import.js168
-rw-r--r--src/components/auth/Invite.js111
-rw-r--r--src/components/auth/Login.js161
-rw-r--r--src/components/auth/Password.js135
-rw-r--r--src/components/auth/Pricing.js130
-rw-r--r--src/components/auth/Signup.js206
-rw-r--r--src/components/auth/Welcome.js69
8 files changed, 1068 insertions, 0 deletions
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
new file mode 100644
index 000000000..2741b8a15
--- /dev/null
+++ b/src/components/auth/AuthLayout.js
@@ -0,0 +1,88 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { RouteTransition } from 'react-router-transition';
5import { intlShape } from 'react-intl';
6
7import Link from '../ui/Link';
8import InfoBar from '../ui/InfoBar';
9
10import { oneOrManyChildElements, globalError as globalErrorPropType } from '../../prop-types';
11import globalMessages from '../../i18n/globalMessages';
12
13@observer
14export default class AuthLayout extends Component {
15 static propTypes = {
16 children: oneOrManyChildElements.isRequired,
17 pathname: PropTypes.string.isRequired,
18 error: globalErrorPropType.isRequired,
19 isOnline: PropTypes.bool.isRequired,
20 isAPIHealthy: PropTypes.bool.isRequired,
21 retryHealthCheck: PropTypes.func.isRequired,
22 isHealthCheckLoading: PropTypes.bool.isRequired,
23 };
24
25 static contextTypes = {
26 intl: intlShape,
27 };
28
29 render() {
30 const {
31 children,
32 pathname,
33 error,
34 isOnline,
35 isAPIHealthy,
36 retryHealthCheck,
37 isHealthCheckLoading,
38 } = this.props;
39 const { intl } = this.context;
40
41 return (
42 <div className="auth">
43 {!isOnline && (
44 <InfoBar
45 type="warning"
46 >
47 <span className="mdi mdi-flash" />
48 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
49 </InfoBar>
50 )}
51 {isOnline && !isAPIHealthy && (
52 <InfoBar
53 type="danger"
54 ctaLabel="Try again"
55 ctaLoading={isHealthCheckLoading}
56 sticky
57 onClick={retryHealthCheck}
58 >
59 <span className="mdi mdi-flash" />
60 {intl.formatMessage(globalMessages.APIUnhealthy)}
61 </InfoBar>
62 )}
63 <div className="auth__layout">
64 <RouteTransition
65 pathname={pathname}
66 atEnter={{ opacity: 0 }}
67 atLeave={{ opacity: 0 }}
68 atActive={{ opacity: 1 }}
69 mapStyles={styles => ({
70 transform: `translateX(${styles.translateX}%)`,
71 opacity: styles.opacity,
72 })}
73 component="span"
74 >
75 {/* Inject globalError into children */}
76 {React.cloneElement(children, {
77 error,
78 })}
79 </RouteTransition>
80 </div>
81 {/* </div> */}
82 <Link to="https://adlk.io" className="auth__adlk" target="_blank">
83 <img src="./assets/images/adlk.svg" alt="" />
84 </Link>
85 </div>
86 );
87 }
88}
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js
new file mode 100644
index 000000000..cf83aa9c8
--- /dev/null
+++ b/src/components/auth/Import.js
@@ -0,0 +1,168 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6import classnames from 'classnames';
7
8import Form from '../../lib/Form';
9import Toggle from '../ui/Toggle';
10import Button from '../ui/Button';
11
12const messages = defineMessages({
13 headline: {
14 id: 'import.headline',
15 defaultMessage: '!!!Import your Franz 4 services',
16 },
17 notSupportedHeadline: {
18 id: 'import.notSupportedHeadline',
19 defaultMessage: '!!!Services not yet supported in Franz 5',
20 },
21 submitButtonLabel: {
22 id: 'import.submit.label',
23 defaultMessage: '!!!Import {count} services',
24 },
25 skipButtonLabel: {
26 id: 'import.skip.label',
27 defaultMessage: '!!!I want add services manually',
28 },
29});
30
31@observer
32export default class Import extends Component {
33 static propTypes = {
34 services: MobxPropTypes.arrayOrObservableArray.isRequired,
35 onSubmit: PropTypes.func.isRequired,
36 isSubmitting: PropTypes.bool.isRequired,
37 inviteRoute: PropTypes.string.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 prepareForm() {
45 const { services } = this.props;
46
47 const config = {
48 fields: {
49 import: [...services.filter(s => s.recipe).map(s => ({
50 add: {
51 default: true,
52 options: s,
53 },
54 }))],
55 },
56 };
57
58 return new Form(config, this.context.intl);
59 }
60
61 submit(e) {
62 const { services } = this.props;
63 e.preventDefault();
64 this.form.submit({
65 onSuccess: (form) => {
66 const servicesImport = form.values().import
67 .map((value, i) => !value.add || services.filter(s => s.recipe)[i])
68 .filter(s => typeof s !== 'boolean');
69
70 this.props.onSubmit({ services: servicesImport });
71 },
72 onError: () => {},
73 });
74 }
75
76 render() {
77 this.form = this.prepareForm();
78 const { intl } = this.context;
79 const { services, isSubmitting, inviteRoute } = this.props;
80
81 const availableServices = services.filter(s => s.recipe);
82 const unavailableServices = services.filter(s => !s.recipe);
83
84 return (
85 <div className="auth__scroll-container">
86 <div className="auth__container auth__container--signup">
87 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
88 <img
89 src="./assets/images/logo.svg"
90 className="auth__logo"
91 alt=""
92 />
93 <h1>
94 {intl.formatMessage(messages.headline)}
95 </h1>
96 <table className="service-table available-services">
97 <tbody>
98 {this.form.$('import').map((service, i) => (
99 <tr
100 key={service.id}
101 className="service-table__row"
102 onClick={() => service.$('add').set(!service.$('add').value)}
103 >
104 <td className="service-table__toggle">
105 <Toggle
106 field={service.$('add')}
107 showLabel={false}
108 />
109 </td>
110 <td className="service-table__column-icon">
111 <img
112 src={availableServices[i].custom_icon || availableServices[i].recipe.icons.svg}
113 className={classnames({
114 'service-table__icon': true,
115 'has-custom-icon': availableServices[i].custom_icon,
116 })}
117 alt=""
118 />
119 </td>
120 <td className="service-table__column-name">
121 {availableServices[i].name !== ''
122 ? availableServices[i].name
123 : availableServices[i].recipe.name}
124 </td>
125 </tr>
126 ))}
127 </tbody>
128 </table>
129 {unavailableServices.length > 0 && (
130 <div className="unavailable-services">
131 <strong>{intl.formatMessage(messages.notSupportedHeadline)}</strong>
132 <p>
133 {services.filter(s => !s.recipe).map((service, i) => (
134 <span key={service.id}>
135 {service.name !== '' ? service.name : service.service}
136 {unavailableServices.length > i + 1 ? ', ' : ''}
137 </span>
138 ))}
139 </p>
140 </div>
141 )}
142
143 {isSubmitting ? (
144 <Button
145 className="auth__button is-loading"
146 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
147 loaded={false}
148 disabled
149 />
150 ) : (
151 <Button
152 type="submit"
153 className="auth__button"
154 label={intl.formatMessage(messages.submitButtonLabel)}
155 />
156 )}
157 <Link
158 to={inviteRoute}
159 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
160 >
161 {intl.formatMessage(messages.skipButtonLabel)}
162 </Link>
163 </form>
164 </div>
165 </div>
166 );
167 }
168}
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js
new file mode 100644
index 000000000..c1d815dcd
--- /dev/null
+++ b/src/components/auth/Invite.js
@@ -0,0 +1,111 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6
7import Form from '../../lib/Form';
8import { email } from '../../helpers/validation-helpers';
9import Input from '../ui/Input';
10import Button from '../ui/Button';
11
12const messages = defineMessages({
13 headline: {
14 id: 'invite.headline.friends',
15 defaultMessage: '!!!Invite 3 of your friends or colleagues',
16 },
17 nameLabel: {
18 id: 'invite.name.label',
19 defaultMessage: '!!!Name',
20 },
21 emailLabel: {
22 id: 'invite.email.label',
23 defaultMessage: '!!!Email address',
24 },
25 submitButtonLabel: {
26 id: 'invite.submit.label',
27 defaultMessage: '!!!Send invites',
28 },
29 skipButtonLabel: {
30 id: 'invite.skip.label',
31 defaultMessage: '!!!I want to do this later',
32 },
33});
34
35@observer
36export default class Invite extends Component {
37 static propTypes = {
38 onSubmit: PropTypes.func.isRequired,
39 };
40
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 form = new Form({
46 fields: {
47 invite: [...Array(3).fill({
48 name: {
49 label: this.context.intl.formatMessage(messages.nameLabel),
50 // value: '',
51 placeholder: this.context.intl.formatMessage(messages.nameLabel),
52 },
53 email: {
54 label: this.context.intl.formatMessage(messages.emailLabel),
55 // value: '',
56 validate: [email],
57 placeholder: this.context.intl.formatMessage(messages.emailLabel),
58 },
59 })],
60 },
61 }, this.context.intl);
62
63 submit(e) {
64 e.preventDefault();
65 this.form.submit({
66 onSuccess: (form) => {
67 this.props.onSubmit({ invites: form.values().invite });
68 },
69 onError: () => {},
70 });
71 }
72
73 render() {
74 const { form } = this;
75 const { intl } = this.context;
76
77 return (
78 <div className="auth__container auth__container--signup">
79 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
80 <img
81 src="./assets/images/logo.svg"
82 className="auth__logo"
83 alt=""
84 />
85 <h1>
86 {intl.formatMessage(messages.headline)}
87 </h1>
88 {form.$('invite').map(invite => (
89 <div className="grid" key={invite.key}>
90 <div className="grid__row">
91 <Input field={invite.$('name')} showLabel={false} />
92 <Input field={invite.$('email')} showLabel={false} />
93 </div>
94 </div>
95 ))}
96 <Button
97 type="submit"
98 className="auth__button"
99 label={intl.formatMessage(messages.submitButtonLabel)}
100 />
101 <Link
102 to="/"
103 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
104 >
105 {intl.formatMessage(messages.skipButtonLabel)}
106 </Link>
107 </form>
108 </div>
109 );
110 }
111}
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
new file mode 100644
index 000000000..015079f02
--- /dev/null
+++ b/src/components/auth/Login.js
@@ -0,0 +1,161 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Button from '../ui/Button';
10import Link from '../ui/Link';
11
12import { globalError as globalErrorPropType } from '../../prop-types';
13
14// import Appear from '../ui/effects/Appear';
15
16const messages = defineMessages({
17 headline: {
18 id: 'login.headline',
19 defaultMessage: '!!!Sign in',
20 },
21 emailLabel: {
22 id: 'login.email.label',
23 defaultMessage: '!!!Email address',
24 },
25 passwordLabel: {
26 id: 'login.password.label',
27 defaultMessage: '!!!Password',
28 },
29 submitButtonLabel: {
30 id: 'login.submit.label',
31 defaultMessage: '!!!Sign in',
32 },
33 invalidCredentials: {
34 id: 'login.invalidCredentials',
35 defaultMessage: '!!!Email or password not valid',
36 },
37 tokenExpired: {
38 id: 'login.tokenExpired',
39 defaultMessage: '!!!Your session expired, please login again.',
40 },
41 serverLogout: {
42 id: 'login.serverLogout',
43 defaultMessage: '!!!Your session expired, please login again.',
44 },
45 signupLink: {
46 id: 'login.link.signup',
47 defaultMessage: '!!!Create a free account',
48 },
49 passwordLink: {
50 id: 'login.link.password',
51 defaultMessage: '!!!Forgot password',
52 },
53});
54
55@observer
56export default class Login extends Component {
57 static propTypes = {
58 onSubmit: PropTypes.func.isRequired,
59 isSubmitting: PropTypes.bool.isRequired,
60 isTokenExpired: PropTypes.bool.isRequired,
61 isServerLogout: PropTypes.bool.isRequired,
62 signupRoute: PropTypes.string.isRequired,
63 passwordRoute: PropTypes.string.isRequired,
64 error: globalErrorPropType.isRequired,
65 };
66
67 static contextTypes = {
68 intl: intlShape,
69 };
70
71 form = new Form({
72 fields: {
73 email: {
74 label: this.context.intl.formatMessage(messages.emailLabel),
75 value: '',
76 validate: [required, email],
77 },
78 password: {
79 label: this.context.intl.formatMessage(messages.passwordLabel),
80 value: '',
81 validate: [required],
82 type: 'password',
83 },
84 },
85 }, this.context.intl);
86
87 submit(e) {
88 e.preventDefault();
89 this.form.submit({
90 onSuccess: (form) => {
91 this.props.onSubmit(form.values());
92 },
93 onError: () => {},
94 });
95 }
96
97 emailField = null;
98
99 render() {
100 const { form } = this;
101 const { intl } = this.context;
102 const {
103 isSubmitting,
104 isTokenExpired,
105 isServerLogout,
106 signupRoute,
107 passwordRoute,
108 error,
109 } = this.props;
110
111 return (
112 <div className="auth__container">
113 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
114 <img
115 src="./assets/images/logo.svg"
116 className="auth__logo"
117 alt=""
118 />
119 <h1>{intl.formatMessage(messages.headline)}</h1>
120 {isTokenExpired && (
121 <p className="error-message center">{intl.formatMessage(messages.tokenExpired)}</p>
122 )}
123 {isServerLogout && (
124 <p className="error-message center">{intl.formatMessage(messages.serverLogout)}</p>
125 )}
126 <Input
127 field={form.$('email')}
128 ref={(element) => { this.emailField = element; }}
129 focus
130 />
131 <Input
132 field={form.$('password')}
133 showPasswordToggle
134 />
135 {error.code === 'invalid-credentials' && (
136 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p>
137 )}
138 {isSubmitting ? (
139 <Button
140 className="auth__button is-loading"
141 buttonType="secondary"
142 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
143 loaded={false}
144 disabled
145 />
146 ) : (
147 <Button
148 type="submit"
149 className="auth__button"
150 label={intl.formatMessage(messages.submitButtonLabel)}
151 />
152 )}
153 </form>
154 <div className="auth__links">
155 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
156 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link>
157 </div>
158 </div>
159 );
160 }
161}
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.js
new file mode 100644
index 000000000..d2b196853
--- /dev/null
+++ b/src/components/auth/Password.js
@@ -0,0 +1,135 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Button from '../ui/Button';
10import Link from '../ui/Link';
11import Infobox from '../ui/Infobox';
12
13const messages = defineMessages({
14 headline: {
15 id: 'password.headline',
16 defaultMessage: '!!!Forgot password',
17 },
18 emailLabel: {
19 id: 'password.email.label',
20 defaultMessage: '!!!Email address',
21 },
22 submitButtonLabel: {
23 id: 'password.submit.label',
24 defaultMessage: '!!!Submit',
25 },
26 successInfo: {
27 id: 'password.successInfo',
28 defaultMessage: '!!!Your new password was sent to your email address',
29 },
30 noUser: {
31 id: 'password.noUser',
32 defaultMessage: '!!!No user affiliated with that email address',
33 },
34 signupLink: {
35 id: 'password.link.signup',
36 defaultMessage: '!!!Create a free account',
37 },
38 loginLink: {
39 id: 'password.link.login',
40 defaultMessage: '!!!Sign in to your account',
41 },
42});
43
44@observer
45export default class Password extends Component {
46 static propTypes = {
47 onSubmit: PropTypes.func.isRequired,
48 isSubmitting: PropTypes.bool.isRequired,
49 signupRoute: PropTypes.string.isRequired,
50 loginRoute: PropTypes.string.isRequired,
51 status: MobxPropTypes.arrayOrObservableArray.isRequired,
52 };
53
54 static contextTypes = {
55 intl: intlShape,
56 };
57
58 form = new Form({
59 fields: {
60 email: {
61 label: this.context.intl.formatMessage(messages.emailLabel),
62 value: '',
63 validate: [required, email],
64 },
65 },
66 }, this.context.intl);
67
68 submit(e) {
69 e.preventDefault();
70 this.form.submit({
71 onSuccess: (form) => {
72 this.props.onSubmit(form.values());
73 },
74 onError: () => {},
75 });
76 }
77
78 render() {
79 const { form } = this;
80 const { intl } = this.context;
81 const {
82 isSubmitting,
83 signupRoute,
84 loginRoute,
85 status,
86 } = this.props;
87
88 return (
89 <div className="auth__container">
90 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
91 <img
92 src="./assets/images/logo.svg"
93 className="auth__logo"
94 alt=""
95 />
96 <h1>{intl.formatMessage(messages.headline)}</h1>
97 {status.length > 0 && status.includes('sent') && (
98 <Infobox
99 type="success"
100 icon="checkbox-marked-circle-outline"
101 >
102 {intl.formatMessage(messages.successInfo)}
103 </Infobox>
104 )}
105 <Input
106 field={form.$('email')}
107 focus
108 />
109 {status.length > 0 && status.includes('no-user') && (
110 <p className="error-message center">{intl.formatMessage(messages.noUser)}</p>
111 )}
112 {isSubmitting ? (
113 <Button
114 className="auth__button is-loading"
115 buttonType="secondary"
116 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
117 loaded={false}
118 disabled
119 />
120 ) : (
121 <Button
122 type="submit"
123 className="auth__button"
124 label={intl.formatMessage(messages.submitButtonLabel)}
125 />
126 )}
127 </form>
128 <div className="auth__links">
129 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
130 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link>
131 </div>
132 </div>
133 );
134 }
135}
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
new file mode 100644
index 000000000..761561a89
--- /dev/null
+++ b/src/components/auth/Pricing.js
@@ -0,0 +1,130 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5// import { Link } from 'react-router';
6
7// import Button from '../ui/Button';
8import Loader from '../ui/Loader';
9import Appear from '../ui/effects/Appear';
10import SubscriptionForm from '../../containers/ui/SubscriptionFormScreen';
11
12const messages = defineMessages({
13 headline: {
14 id: 'pricing.headline',
15 defaultMessage: '!!!Support Franz',
16 },
17 monthlySupportLabel: {
18 id: 'pricing.support.label',
19 defaultMessage: '!!!Select your support plan',
20 },
21 submitButtonLabel: {
22 id: 'pricing.submit.label',
23 defaultMessage: '!!!Support the development of Franz',
24 },
25 skipPayment: {
26 id: 'pricing.link.skipPayment',
27 defaultMessage: '!!!I don\'t want to support the development of Franz.',
28 },
29});
30
31@observer
32export default class Signup extends Component {
33 static propTypes = {
34 donor: MobxPropTypes.objectOrObservableObject.isRequired,
35 isLoading: PropTypes.bool.isRequired,
36 isLoadingUser: PropTypes.bool.isRequired,
37 onCloseSubscriptionWindow: PropTypes.func.isRequired,
38 skipAction: PropTypes.func.isRequired,
39 };
40
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 render() {
46 const {
47 donor,
48 isLoading,
49 isLoadingUser,
50 onCloseSubscriptionWindow,
51 skipAction,
52 } = this.props;
53 const { intl } = this.context;
54
55 return (
56 <div className="auth__scroll-container">
57 <div className="auth__container auth__container--signup">
58 <form className="franz-form auth__form">
59 <img
60 src="./assets/images/sm.png"
61 className="auth__logo auth__logo--sm"
62 alt=""
63 />
64 <h1>{intl.formatMessage(messages.headline)}</h1>
65 <div className="auth__letter">
66 {isLoadingUser && (
67 <p>Loading</p>
68 )}
69 {!isLoadingUser && (
70 donor.amount ? (
71 <span>
72 <p>
73 Thank you so much for your previous donation of <strong>$ {donor.amount}</strong>.
74 <br />
75 Your support allowed us to get where we are today.
76 <br />
77 </p>
78 <p>
79 As an early supporter, you get <strong>a lifetime premium supporter license</strong> without any
80 additional charges.
81 </p>
82 <p>
83 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
84 <br /><br />
85 </p>
86 </span>
87 ) : (
88 <span>
89 <p>
90 We built Franz with a lot of effort, manpower and love,
91 to bring you the best messaging experience.
92 <br />
93 </p>
94 <p>
95 Getting a Franz Premium Supporter License will allow us to keep improving Franz for you.
96 </p>
97 </span>
98 )
99 )}
100 <p>
101 Thanks for being a hero.
102 </p>
103 <p>
104 <strong>Stefan Malzner</strong>
105 </p>
106 </div>
107 <Loader loaded={!isLoading}>
108 <Appear transitionName="slideDown">
109 <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span>
110 <SubscriptionForm
111 onCloseWindow={onCloseSubscriptionWindow}
112 showSkipOption
113 skipAction={skipAction}
114 hideInfo={Boolean(donor.amount)}
115 skipButtonLabel={intl.formatMessage(messages.skipPayment)}
116 />
117 {/* <Link
118 to={inviteRoute}
119 className="franz-form__button franz-form__button--secondary auth__button auth__button--skip"
120 >
121 {intl.formatMessage(messages.skipPayment)}
122 </Link> */}
123 </Appear>
124 </Loader>
125 </form>
126 </div>
127 </div>
128 );
129 }
130}
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
new file mode 100644
index 000000000..71ca16111
--- /dev/null
+++ b/src/components/auth/Signup.js
@@ -0,0 +1,206 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Form from '../../lib/Form';
7import { required, email, minLength } from '../../helpers/validation-helpers';
8import Input from '../ui/Input';
9import Radio from '../ui/Radio';
10import Button from '../ui/Button';
11import Link from '../ui/Link';
12
13import { globalError as globalErrorPropType } from '../../prop-types';
14
15const messages = defineMessages({
16 headline: {
17 id: 'signup.headline',
18 defaultMessage: '!!!Sign up',
19 },
20 firstnameLabel: {
21 id: 'signup.firstname.label',
22 defaultMessage: '!!!Firstname',
23 },
24 lastnameLabel: {
25 id: 'signup.lastname.label',
26 defaultMessage: '!!!Lastname',
27 },
28 emailLabel: {
29 id: 'signup.email.label',
30 defaultMessage: '!!!Email address',
31 },
32 companyLabel: {
33 id: 'signup.company.label',
34 defaultMessage: '!!!Company',
35 },
36 passwordLabel: {
37 id: 'signup.password.label',
38 defaultMessage: '!!!Password',
39 },
40 legalInfo: {
41 id: 'signup.legal.info',
42 defaultMessage: '!!!By creating a Franz account you accept the',
43 },
44 terms: {
45 id: 'signup.legal.terms',
46 defaultMessage: '!!!Terms of service',
47 },
48 privacy: {
49 id: 'signup.legal.privacy',
50 defaultMessage: '!!!Privacy Statement',
51 },
52 submitButtonLabel: {
53 id: 'signup.submit.label',
54 defaultMessage: '!!!Create account',
55 },
56 loginLink: {
57 id: 'signup.link.login',
58 defaultMessage: '!!!Already have an account, sign in?',
59 },
60 emailDuplicate: {
61 id: 'signup.emailDuplicate',
62 defaultMessage: '!!!A user with that email address already exists',
63 },
64});
65
66@observer
67export default class Signup extends Component {
68 static propTypes = {
69 onSubmit: PropTypes.func.isRequired,
70 isSubmitting: PropTypes.bool.isRequired,
71 loginRoute: PropTypes.string.isRequired,
72 error: globalErrorPropType.isRequired,
73 };
74
75 static contextTypes = {
76 intl: intlShape,
77 };
78
79 form = new Form({
80 fields: {
81 accountType: {
82 value: 'individual',
83 validate: [required],
84 options: [{
85 value: 'individual',
86 label: 'Individual',
87 }, {
88 value: 'non-profit',
89 label: 'Non-Profit',
90 }, {
91 value: 'company',
92 label: 'Company',
93 }],
94 },
95 firstname: {
96 label: this.context.intl.formatMessage(messages.firstnameLabel),
97 value: '',
98 validate: [required],
99 },
100 lastname: {
101 label: this.context.intl.formatMessage(messages.lastnameLabel),
102 value: '',
103 validate: [required],
104 },
105 email: {
106 label: this.context.intl.formatMessage(messages.emailLabel),
107 value: '',
108 validate: [required, email],
109 },
110 organization: {
111 label: this.context.intl.formatMessage(messages.companyLabel),
112 value: '', // TODO: make required when accountType: company
113 },
114 password: {
115 label: this.context.intl.formatMessage(messages.passwordLabel),
116 value: '',
117 validate: [required, minLength(6)],
118 type: 'password',
119 },
120 },
121 }, this.context.intl);
122
123 submit(e) {
124 e.preventDefault();
125 this.form.submit({
126 onSuccess: (form) => {
127 this.props.onSubmit(form.values());
128 },
129 onError: () => {},
130 });
131 }
132
133 render() {
134 const { form } = this;
135 const { intl } = this.context;
136 const { isSubmitting, loginRoute, error } = this.props;
137
138 return (
139 <div className="auth__scroll-container">
140 <div className="auth__container auth__container--signup">
141 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
142 <img
143 src="./assets/images/logo.svg"
144 className="auth__logo"
145 alt=""
146 />
147 <h1>{intl.formatMessage(messages.headline)}</h1>
148 <Radio field={form.$('accountType')} showLabel={false} />
149 <div className="grid__row">
150 <Input field={form.$('firstname')} focus />
151 <Input field={form.$('lastname')} />
152 </div>
153 <Input field={form.$('email')} />
154 <Input
155 field={form.$('password')}
156 showPasswordToggle
157 scorePassword
158 />
159 {form.$('accountType').value === 'company' && (
160 <Input field={form.$('organization')} />
161 )}
162 {error.code === 'email-duplicate' && (
163 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p>
164 )}
165 {isSubmitting ? (
166 <Button
167 className="auth__button is-loading"
168 label={`${intl.formatMessage(messages.submitButtonLabel)} ...`}
169 loaded={false}
170 disabled
171 />
172 ) : (
173 <Button
174 type="submit"
175 className="auth__button"
176 label={intl.formatMessage(messages.submitButtonLabel)}
177 />
178 )}
179 <p className="legal">
180 {intl.formatMessage(messages.legalInfo)}
181 <br />
182 <Link
183 to="http://meetfranz.com/terms"
184 target="_blank"
185 className="link"
186 >
187 {intl.formatMessage(messages.terms)}
188 </Link>
189 &nbsp;&amp;&nbsp;
190 <Link
191 to="http://meetfranz.com/privacy"
192 target="_blank"
193 className="link"
194 >
195 {intl.formatMessage(messages.privacy)}
196 </Link>.
197 </p>
198 </form>
199 <div className="auth__links">
200 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
201 </div>
202 </div>
203 </div>
204 );
205 }
206}
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js
new file mode 100644
index 000000000..06b10ecfe
--- /dev/null
+++ b/src/components/auth/Welcome.js
@@ -0,0 +1,69 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Link from '../ui/Link';
7
8const messages = defineMessages({
9 signupButton: {
10 id: 'welcome.signupButton',
11 defaultMessage: '!!!Create a free account',
12 },
13 loginButton: {
14 id: 'welcome.loginButton',
15 defaultMessage: '!!!Login to your account',
16 },
17});
18
19@observer
20export default class Login extends Component {
21 static propTypes = {
22 loginRoute: PropTypes.string.isRequired,
23 signupRoute: PropTypes.string.isRequired,
24 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
25 };
26
27 static contextTypes = {
28 intl: intlShape,
29 };
30
31 render() {
32 const { intl } = this.context;
33 const {
34 loginRoute,
35 signupRoute,
36 recipes,
37 } = this.props;
38
39 return (
40 <div className="welcome">
41 <div className="welcome__content">
42 <img src="./assets/images/logo.svg" className="welcome__logo" alt="" />
43 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */}
44 <div className="welcome__text">
45 <h1>Franz</h1>
46 </div>
47 </div>
48 <div className="welcome__buttons">
49 <Link to={signupRoute} className="button">
50 {intl.formatMessage(messages.signupButton)}
51 </Link>
52 <Link to={loginRoute} className="button">
53 {intl.formatMessage(messages.loginButton)}
54 </Link>
55 </div>
56 <div className="welcome__featured-services">
57 {recipes.map(recipe => (
58 <img
59 key={recipe.id}
60 src={recipe.icons.svg}
61 className="welcome__featured-service"
62 alt=""
63 />
64 ))}
65 </div>
66 </div>
67 );
68 }
69}