summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--web-ui/package.json3
-rw-r--r--web-ui/react/dummy.json1
-rw-r--r--web-ui/react/src/index.js106
4 files changed, 89 insertions, 24 deletions
diff --git a/.gitignore b/.gitignore
index 0c347bf8..d60eade0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@
web-ui/node_modules
web-ui/app/bower_components
web-ui/target
+web-ui/react/bundle.css
+web-ui/react/bundle.js
+web-ui/react/lib/
.tmp
.sass-cache/
dist/
diff --git a/web-ui/package.json b/web-ui/package.json
index c85889e9..7b454d8c 100644
--- a/web-ui/package.json
+++ b/web-ui/package.json
@@ -52,7 +52,8 @@
"immutable": "^3.8.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
- "redux": "^3.6.0"
+ "redux": "^3.6.0",
+ "whatwg-fetch": "^2.0.0"
},
"babel": {
"presets": [
diff --git a/web-ui/react/dummy.json b/web-ui/react/dummy.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/web-ui/react/dummy.json
@@ -0,0 +1 @@
+{}
diff --git a/web-ui/react/src/index.js b/web-ui/react/src/index.js
index ae52867f..35de6407 100644
--- a/web-ui/react/src/index.js
+++ b/web-ui/react/src/index.js
@@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Map} from 'immutable';
+import 'whatwg-fetch';
class PixelatedComponent extends React.Component {
@@ -10,19 +11,37 @@ class PixelatedComponent extends React.Component {
}
componentWillMount() {
- console.debug('mounting', this);
this.unsubscribe = this.props.store.subscribe(() => this._updateStateFromStore());
this._updateStateFromStore();
}
componentWillUnmount() {
- console.debug('unmounting', this);
this.unsubscribe()
}
}
-class InviteCodeForm extends PixelatedComponent {
+class PixelatedForm extends PixelatedComponent {
+ _fetchAndDispatch(url, actionProperties) {
+ const immutableActionProperties = new Map(actionProperties);
+ this.props.store.dispatch(immutableActionProperties.merge({status: 'STARTED'}).toJS());
+ fetch(url).then((response) => {
+ console.debug('got a reply', response);
+ return response.json()
+ }).then((json) => {
+ console.debug('got json', json);
+ setTimeout(() => {
+ this.props.store.dispatch(immutableActionProperties.merge({status: 'SUCCESS', json: json}).toJS());
+ }, 3000);
+ }).catch((error) => {
+ console.error('something went wrong', error);
+ this.props.store.dispatch(immutableActionProperties.merge({status: 'ERROR', error: error}).toJS());
+ });
+ }
+}
+
+
+class InviteCodeForm extends PixelatedForm {
render() {
return (
<form onSubmit={this._handleClick.bind(this)}>
@@ -38,12 +57,12 @@ class InviteCodeForm extends PixelatedComponent {
_handleClick(event) {
event.stopPropagation();
event.preventDefault();
- this.props.store.dispatch({type: 'SUBMIT_INVITE_CODE'});
+ this.props.store.dispatch({type: 'SUBMIT_INVITE_CODE', inviteCode: event.target['invite-code'].value});
}
}
-class CreateAccountForm extends PixelatedComponent {
+class CreateAccountForm extends PixelatedForm {
render() {
return (
<form onSubmit={this._handleClick.bind(this)}>
@@ -58,11 +77,6 @@ class CreateAccountForm extends PixelatedComponent {
<label className="animated-label" htmlFor="password">password</label>
</div>
- <div className="field-group">
- <input type="text" name="name" className="name" required/>
- <label className="animated-label" htmlFor="name">name</label>
- </div>
-
<input type="submit" value="Create my account" className="blue-button validation-link" />
</form>
);
@@ -71,27 +85,44 @@ class CreateAccountForm extends PixelatedComponent {
_handleClick(event) {
event.stopPropagation();
event.preventDefault();
- this.props.store.dispatch({type: 'SUBMIT_CREATE_ACCOUNT'});
+ this.props.store.dispatch({type: 'SUBMIT_CREATE_ACCOUNT', username: event.target['username'].value, password: event.target['password'].value});
}
}
-class BackupEmailForm extends PixelatedComponent {
+class BackupEmailForm extends PixelatedForm {
render() {
return (
<form onSubmit={this._handleClick.bind(this)}>
- <span className="domain-label"> @domain.com </span>
<div className="field-group">
- <input type="text" name="username" className="username" required/>
- <label className="animated-label" htmlFor="username">username</label>
+ <input type="text" name="backup-email" required/>
+ <label className="animated-label" htmlFor="password">type your backup email</label>
</div>
- <div className="field-group">
- <input type="password" name="password" className="password" required/>
- <label className="animated-label" htmlFor="password">password</label>
- </div>
+ <input type="submit" value="Send Email" className="blue-button validation-link" />
+ <p className="link-message">
+ <a href="#" className="validation-link">I didn't receive anything. Send the email again</a>
+ </p>
+ </form>
+ );
+ }
- <input type="submit" value="Create my account" className="blue-button validation-link" />
+ _handleClick(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ this._fetchAndDispatch('dummy.json', {type: 'SUBMIT_BACKUP_EMAIL', backupEmail: event.target['backup-email'].value});
+ }
+}
+
+
+class BackupEmailSentForm extends PixelatedForm {
+ render() {
+ return (
+ <form onSubmit={this._handleClick.bind(this)}>
+ {this.state.isFetching || <a href="/" className="blue-button">I received the codes. <br/>Go to my inbox</a>}
+ <p className="link-message">
+ <a href="#">I didn't receive anything. Send the email again</a>
+ </p>
</form>
);
}
@@ -99,7 +130,6 @@ class BackupEmailForm extends PixelatedComponent {
_handleClick(event) {
event.stopPropagation();
event.preventDefault();
- this.props.store.dispatch({type: 'SUBMIT_CREATE_ACCOUNT'});
}
}
@@ -110,6 +140,7 @@ class SignUp extends PixelatedComponent {
<div>
<div className="message">
<h1>{this.state.header}</h1>
+ {this.state.icon}
<p>{this.state.summary}</p>
</div>
<div className="form-container">
@@ -124,16 +155,19 @@ class SignUp extends PixelatedComponent {
case 'invite_code': return <InviteCodeForm store={store} />;
case 'create_account': return <CreateAccountForm store={store} />;
case 'backup_email': return <BackupEmailForm store={store} />;
- default: throw Exception('TODO');
+ case 'backup_email_sent': return <BackupEmailSentForm store={store} />;
+ default: throw Error('TODO');
}
}
}
const initialState = new Map({
+ isFetching: false,
form: 'invite_code',
header: 'Welcome',
- summary: ['Do you have an invite code?', <br/>, 'Type it below'],
+ icon: null,
+ summary: ['Do you have an invite code?', <br key='br1' />, 'Type it below'],
});
@@ -141,16 +175,42 @@ const store = createStore((state=initialState, action) => {
switch (action.type) {
case 'SUBMIT_INVITE_CODE':
return state.merge({
+ inviteCode: action.inviteCode,
form: 'create_account',
header: 'Create your account',
summary: 'Choose your username, and be careful about your password, it must be strong and easy to remember. If you have a password manager, we strongly advise you to use one.',
});
case 'SUBMIT_CREATE_ACCOUNT':
return state.merge({
+ username: action.username,
+ password: action.password,
form: 'backup_email',
header: 'In case you lose your password...',
summary: 'Set up a backup email account. You\'ll receive an email with a code so you can recover your account in the future, other will be sent to your account administrator.',
});
+ case 'SUBMIT_BACKUP_EMAIL':
+ switch (action.status) {
+ case 'STARTED':
+ return state.merge({
+ isFetching: true,
+ backupEmail: action.backupEmail,
+ form: 'backup_email_sent',
+ icon: <p><img key="img1" src="images/sent_email.png" className="sent-email-icon"/></p>,
+ summary: 'An email was sent to the email you provided. Check your spam folder, just in case.',
+ });
+ case 'SUCCESS':
+ return state.merge({
+ isFetching: false,
+ });
+ case 'ERROR':
+ return state.merge({
+ isFetching: false,
+ });
+ default:
+ return state;
+ }
+ case 'SUBMIT_BACKUP_EMAIL_SENT':
+ return state.merge({});
default:
return state;
}