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 { _updateStateFromStore() { this.setState(this.props.store.getState().toJS()); } componentWillMount() { this.unsubscribe = this.props.store.subscribe(() => this._updateStateFromStore()); this._updateStateFromStore(); } componentWillUnmount() { this.unsubscribe(); } } 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) => { return response.json(); }).then((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() { let className = "blue-button validation-link"; if(!this.state.inviteCodeValidation) { className = className + " disabled"; } return ( <form onSubmit={this._handleClick.bind(this)}> <div className="field-group"> <input type="text" name="invite-code" className="invite-code" onChange={this._handleInputEmpty.bind(this)} required/> <label className="animated-label" htmlFor="invite-code">invite code</label> </div> <input type="submit" value="Get Started" className={className} /> </form> ); } _handleClick(event) { event.stopPropagation(); event.preventDefault(); this.props.store.dispatch({type: 'SUBMIT_INVITE_CODE', inviteCode: event.target['invite-code'].value}); } _handleInputEmpty(event) { this.props.store.dispatch({type: 'VALIDATE_INVITE_CODE', inviteCode: event.target.value}); } } class CreateAccountForm 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> </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="Create my account" className="blue-button validation-link" /> </form> ); } _handleClick(event) { event.stopPropagation(); event.preventDefault(); this.props.store.dispatch({type: 'SUBMIT_CREATE_ACCOUNT', username: event.target.username.value, password: event.target.password.value}); } } class BackupEmailForm extends PixelatedForm { render() { return ( <form onSubmit={this._handleClick.bind(this)}> <div className="field-group"> <input type="text" name="backup-email" required/> <label className="animated-label" htmlFor="password">type your backup email</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> ); } _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> ); } _handleClick(event) { event.stopPropagation(); event.preventDefault(); } } class SignUp extends PixelatedComponent { render() { return ( <div> <div className="message"> <h1>{this.state.header}</h1> {this.state.icon} <p>{this.state.summary}</p> </div> <div className="form-container"> {this._form()} </div> </div> ); } _form() { switch(this.state.form) { case 'invite_code': return <InviteCodeForm store={store} />; case 'create_account': return <CreateAccountForm store={store} />; case 'backup_email': return <BackupEmailForm store={store} />; case 'backup_email_sent': return <BackupEmailSentForm store={store} />; default: throw Error('TODO'); } } } const initialState = new Map({ isFetching: false, form: 'invite_code', header: 'Welcome', icon: null, summary: ['Do you have an invite code?', <br key='br1' />, 'Type it below'], }); 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({}); case 'VALIDATE_INVITE_CODE': return state.merge({ inviteCodeValidation: Boolean(action.inviteCode) }); default: return state; } }); ReactDOM.render( <SignUp store={store}/>, document.getElementById('app') );