Created
November 27, 2016 01:06
-
-
Save DDR0/9cd874fcab425adf47325f99acf6ad01 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-env browser */ | |
/* global ce, Promise, google, moment, DOMContentLoadedPromise, GoogleMapLoadedPromise, loggedIn, loggedInAs, Pikaday, React, ReactDOM */ | |
"use strict"; | |
ce.flags.pageHandlesLogout = true; | |
//Set up step logic, and step interaction with the hash. | |
DOMContentLoadedPromise.then(()=>{ | |
//Skip a few steps on desktop, since it combines 2, 3 and 4 into one step. | |
document.querySelector('.next.desktop-only').addEventListener('click', | |
() => document.body.setAttribute('step', {1:2,2:5,3:5,4:5,5:6}[+document.body.getAttribute('step')]) ); | |
document.querySelector('.back.mobile-only').addEventListener('click', | |
() => document.body.setAttribute('step', +document.body.getAttribute('step')-1) ); | |
document.querySelector('.next.mobile-only').addEventListener('click', | |
() => document.body.setAttribute('step', +document.body.getAttribute('step')+1) ); | |
[].forEach.call(document.querySelectorAll('#step-display > label'), | |
label => label.addEventListener('click', | |
() => document.body.setAttribute('step', label.getAttribute('step')) ) ); | |
//Set hash based on step, so we can go back/forward between steps. | |
if(window.location.hash.slice(1) >= 2) { | |
document.body.setAttribute('step', window.location.hash.slice(1)); } | |
try { | |
new MutationObserver(function(mutations) { | |
window.location.hash = document.body.getAttribute('step'); | |
}).observe( | |
document.body, | |
{ attributes: true, attributeFilter:['step'] } | |
); | |
} catch(e) { console.warn('error setting url to step', e); } | |
window.addEventListener('hashchange', ()=> | |
window.location.hash.slice(1) !== document.body.getAttribute('step') | |
? document.body.setAttribute('step', window.location.hash.slice(1)) | |
: null | |
); | |
}); | |
//Notes: | |
//The approach we will take will be to render the entire page, then display parts of it | |
// using CSS and a bit of javascript to set the classes. This will keep the state of | |
// the page as simple as possible, and let us use the same code for mobile and desktop. | |
//React will keep the values entered in sync across all the fields. | |
class Steps extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
rent: null, | |
paymentDueDate: moment().add(4, 'days').endOf('month').toDate(), | |
frequency: null, | |
email: null, | |
}; | |
} | |
updateField = (field, event) => { | |
const newState = {}; | |
newState[field] = event.target ? event.target.value : event; | |
this.setState(newState); | |
} | |
render() { | |
return <div> | |
<Pitch paymentDetails={this.state} updateField={this.updateField} />{/*1, scrolls to mobile 1-2*/} | |
<RentDestination/>{/*2*/} | |
<RentSource/> | |
<CreditCard/> | |
<Confirmation/>{/*3*/} | |
<ThankYou/>{/*4*/} | |
</div>; | |
} | |
} | |
DOMContentLoadedPromise.then( ()=>{ | |
ReactDOM.render(<Steps/>, document.querySelector('#step-container')); | |
}); | |
class Pitch extends React.Component { | |
constructor(props) { | |
super(props); | |
} | |
static propTypes = { | |
paymentDetails: React.PropTypes.object, | |
updateField: React.PropTypes.func, | |
} | |
paymentDueDatePikaday = null; | |
bindPaymentDueDatePikaday = input => { | |
this.paymentDueDatePikaday = new Pikaday({ | |
field: input, | |
minDate: moment().add(4, 'days').toDate(), | |
onSelect: this.props.updateField.bind(0,'paymentDueDate'), | |
format: 'YYYY/MM/DD', | |
}); | |
} | |
render() { | |
return <div id="pitch" step="1"> | |
<h1>pay your rent online</h1> | |
<div className="cell-table"> | |
<div> | |
<label>rent</label> | |
<span><input | |
name="rent" | |
placeholder="number test" | |
type="number" //DDR 2016-11-24: use type=number, because we can't just specify inputType="numeric" because support is almost non-existant | |
step="0.01" //Specify a small step, because FF fails to validate the number otherwise unless you enter a digit in the integer part of the number after you've entered the decimal part. | |
noValidate | |
value={this.props.paymentDetails.rent||''} | |
onChange={this.props.updateField.bind(0,'rent')} | |
/></span>{/*Gotta have spans here, because they're the table cell which the content is vertically centered in. No span, no center.*/} | |
<span>/month</span> | |
</div> | |
<div> | |
<label>rent</label> | |
<span><MonetaryInput | |
amount={this.props.paymentDetails.rent||''} | |
update={this.props.updateField.bind(0,'rent')} | |
placeholder="enter your monthly rent here" | |
/></span>{/*Gotta have spans here, because they're the table cell which the content is vertically centered in. No span, no center.*/} | |
<span>/month</span> | |
</div> | |
<div> | |
<label>pay on</label> | |
<span><input | |
name="due-date" | |
placeholder={moment().add(4, 'days').endOf('month').format('YYYY/MM/DD') /*We require four days of lead time to get the cheques in the mail, so prompt ahead.*/} | |
value={this.props.paymentDetails.paymentDueDate ? moment(this.props.paymentDetails.paymentDueDate).format('YYYY/MM/DD') : ''} | |
onChange={event => this.props.updateField('paymentDueDate', event.target.value ? new Date(event.target.value) : null)} | |
ref={this.bindPaymentDueDatePikaday} | |
/></span> | |
<span></span> | |
</div> | |
<div> | |
<label>frequency</label> | |
<div> | |
<div className="radio-select"> | |
<label className="button"> | |
<input | |
type="radio" | |
name="frequency" | |
value="monthly" | |
checked={this.props.paymentDetails.frequency === 'monthly'} | |
onChange={this.props.updateField.bind(0,'frequency')} /> | |
<span>monthly</span> | |
</label> | |
<label className="button"> | |
<input | |
type="radio" | |
name="frequency" | |
value="once" | |
checked={this.props.paymentDetails.frequency === 'once'} | |
onChange={this.props.updateField.bind(0,'frequency')} /> | |
<span>just once</span> | |
</label> | |
</div> | |
</div> | |
<span></span> | |
</div> | |
<div> | |
<label>email</label> | |
<span><input | |
name="tenant-email" | |
placeholder="enter your email address" | |
value={this.props.paymentDetails.email||''} | |
onChange={this.props.updateField.bind(0,'email')} | |
/></span> | |
<span></span> | |
</div> | |
</div> | |
<button onClick={()=>document.body.setAttribute('step', 2)}>get started now</button> | |
<div className="hr"></div> | |
<div className="about-copy"> | |
<h2>pay rent online, easily!</h2> | |
<p className="blurb">Pay rent with any credit card, <strong>collect your points/miles/cashback</strong>, and we'll mail that cheque your landlord loves so much.</p> | |
<p className="bullet-point">powered by Canadian banks and credit card processors</p>{/*Can't use an actual <ul> here because it doesn't wrap around bank icons.*/} | |
<div className="bank-icons">bank icons<br/>go here</div> | |
<p className="bullet-point">your landlord doesn't have to use echopay</p> | |
<p className="bullet-point">full record available for you to prove your history</p> | |
<p className="bullet-point">set and forget, never miss a payment</p> | |
</div> | |
</div>; | |
} | |
} | |
//This input is /sketchy/, try deleting the decimal point or typing some characters and backspacing. | |
class MonetaryInput extends React.Component { | |
constructor(props) { | |
super(props); | |
this.lastGoodAmount = props.amount; | |
} | |
static propTypes = { | |
amount: React.PropTypes.string, | |
update: React.PropTypes.func, | |
placeholder: React.PropTypes.string, | |
} | |
lastGoodAmount = 0; | |
shouldUpdate = true; | |
update = event => { | |
if(event.target.validity && event.target.validity.valid) { | |
this.lastGoodAmount = event.target.value; | |
} | |
this.props.update(event.target.value || this.lastGoodAmount); | |
} | |
render() { | |
return <input | |
name="rent" | |
placeholder={this.props.placeholder} | |
type="number" //DDR 2016-11-24: use type=number, because we can't just specify inputType="numeric" because support is almost non-existant | |
step="any" //Specify a small step, because FF fails to validate the number otherwise unless you enter a digit in the integer part of the number after you've entered the decimal part. | |
value={this.props.amount||''} | |
onChange={this.update} | |
/>; | |
} | |
} | |
class RentDestination extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
}; | |
} | |
render() { | |
return <div step="2"> | |
Rent destination | |
</div>; | |
} | |
} | |
class RentSource extends React.Component { | |
render() { | |
return <div step="3"> | |
Rent source | |
</div>; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
}; | |
} | |
} | |
class CreditCard extends React.Component { | |
render() { | |
return <div step="4"> | |
Credit card details | |
</div>; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
}; | |
} | |
} | |
class Confirmation extends React.Component { | |
render() { | |
return <div step="5"> | |
Confirmation | |
</div>; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
}; | |
} | |
} | |
class ThankYou extends React.Component { | |
render() { | |
return <div step="6"> | |
Thank You | |
</div>; | |
} | |
constructor(props) { | |
super(props); | |
this.state = { | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment