Skip to content

Instantly share code, notes, and snippets.

@DDR0
Created November 27, 2016 01:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DDR0/9cd874fcab425adf47325f99acf6ad01 to your computer and use it in GitHub Desktop.
Save DDR0/9cd874fcab425adf47325f99acf6ad01 to your computer and use it in GitHub Desktop.
/* 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