Skip to content

Instantly share code, notes, and snippets.

@jacobmoyle
Created March 14, 2018 05:47
Show Gist options
  • Select an option

  • Save jacobmoyle/7d4fa727f14006f7453640c98b564dd9 to your computer and use it in GitHub Desktop.

Select an option

Save jacobmoyle/7d4fa727f14006f7453640c98b564dd9 to your computer and use it in GitHub Desktop.
import { RSAA, getJSON } from "redux-api-middleware";
import { openDialog } from "redux-dialog-extended";
import { fromJS } from "immutable";
import {
hasBilling,
showCreditBanner,
showCardExpiredBanner
} from "../helpers/discovery";
import * as selectors from "../selectors";
export const GET_BILLING_REQUEST = "GET_BILLING_REQUEST";
export const GET_BILLING_SUCCESS = "GET_BILLING_SUCCESS";
export const GET_BILLING_FAILURE = "GET_BILLING_FAILURE";
export const SET_BILLING_REQUEST = "SET_BILLING_REQUEST";
export const SET_BILLING_SUCCESS = "SET_BILLING_SUCCESS";
export const SET_BILLING_FAILURE = "SET_BILLING_FAILURE";
export const fetchBilling = () => ({
[RSAA]: {
endpoint: "/billing",
method: "GET",
types: [
GET_BILLING_REQUEST,
{
type: GET_BILLING_SUCCESS,
payload: (action, state, res) =>
getJSON(res).then(json => {
debugger;
return json;
})
},
{
type: GET_BILLING_FAILURE,
payload: (action, state, res) => {
// TODO: figure out why this is not getting added automatically
res.name = "ApiError";
return res;
},
meta: {
handler: handleBillingError,
errorMessage: "There was a problem fetching billing information."
}
}
],
bailout: state => hasBilling(state.discovery.get("billing"))
}
});
export const addBilling = token => ({
[RSAA]: {
endpoint: "/billing/stripe",
method: "POST",
body: JSON.stringify({
token
}),
types: [
SET_BILLING_REQUEST,
{
type: SET_BILLING_SUCCESS,
payload: (action, state, res) => res
},
{
type: SET_BILLING_FAILURE,
meta: {
errorMessage: "There was a problem adding a billing method.",
showModal: true
}
}
]
}
});
/* eslint-disable react/display-name */
import React from "react";
import { fromJS } from "immutable";
import xss from "xss";
import { convertToDollars, convertToCents } from "../helpers/currency";
import { convertHtmlToJson, convertJsonToHtml } from "../helpers/string";
import { defaultBudget, minimumBudget } from "../constants/config.json";
import AboutYouWizard from "../components/AboutYouWizard";
import AddJobForm from "../components/AddJobForm";
import PaymentForm from "../components/PaymentForm";
import BudgetForm from "../components/BudgetForm";
const content = {
aboutYou: (props, context) => {
return (
<AboutYouWizard
initialValues={{ can_contact: true }}
onPageChange={page => context.handleAboutChange(page)}
/>
);
},
addJob: ({
currentJob,
actionType,
aboutYouValues,
addJobValues,
setCurrentJob
}) => {
let addJobInitialValues = {
hasApplyUrl: currentJob
? currentJob.get("hasApplyUrl") || currentJob.has("apply_url")
: true,
contact_email: currentJob
? currentJob.get("contact_email")
: aboutYouValues && aboutYouValues.email,
description:
currentJob && convertHtmlToJson(currentJob.get("description"))
};
if (currentJob) {
addJobInitialValues = { ...currentJob.toJS(), ...addJobInitialValues };
}
return (
<AddJobForm
type={actionType}
initialValues={addJobInitialValues}
editedValues={addJobValues}
setCurrentJob={setCurrentJob}
/>
);
},
budget: ({
currentJob,
markedJobs,
budgetValues,
actionType,
isOnboarding
}) => {
const jobs = currentJob ? fromJS([currentJob]) : markedJobs;
/*
* TODO: removing end date till we need it
* endDate: currentJob.get('date') || moment().add(1, 'months')
*/
const budgetInitialValues = {
budget:
convertToDollars(currentJob && currentJob.get("budget")) ||
defaultBudget
};
return (
<BudgetForm
type={isOnboarding ? "onboarding" : actionType}
jobs={jobs}
initialValues={budgetInitialValues}
editedValues={budgetValues}
/>
);
},
payment: (props, context) => {
const { formError } = props;
const initialValues = context &&
context.state &&
context.state.user && {
user: {
...context.state.user.toJS()
}
};
const isUpdate = context && context.state && context.state.isUpdate;
const onStripeFieldChange = valid =>
context.setState({ stripeFieldsValid: valid });
return (
<PaymentForm
initialValues={initialValues}
isUpdate={isUpdate ? isUpdate : undefined}
formErrorMessage={formError}
onStripeFieldChange={onStripeFieldChange}
/>
);
}
};
const submit = (steps, props) =>
steps.map(step => {
const {
currentJob,
actionType,
budgetValues,
saveJobs,
fetchJobs,
billing,
isOnboarding,
setCurrentJob,
context
} = props;
switch (step) {
case "aboutYou": {
const { aboutYouValues, signup } = props;
return signup(aboutYouValues);
}
case "addJob": {
const { addJobValues, addJobs } = props;
const processedValues = fromJS(addJobValues)
.set("description", convertJsonToHtml(xss(addJobValues.description)))
.toJS();
if (actionType === "edit") {
return saveJobs([processedValues]).then(() =>
fetchJobs(undefined, undefined, true)
);
} else {
const budget = budgetValues && budgetValues.budget;
return addJobs(
setBudgetAndStatus([processedValues], budget, billing)
).then(res => {
if (res.error) {
return res;
}
return fetchJobs(undefined, undefined, true).then(({ payload }) => {
if (isOnboarding && payload.jobs) {
return setCurrentJob(payload.jobs.first(), "add");
}
});
});
}
}
case "budget":
if (actionType !== "add" && !steps.includes("addJob")) {
const { markedJobs, onBudgetSubmit, presetJobs } = props;
const jobs = currentJob ? fromJS([currentJob]) : markedJobs;
const budget = budgetValues && budgetValues.budget;
const updatedJobs = setBudgetAndStatus(jobs, budget, billing).toJS();
presetJobs(updatedJobs);
return onBudgetSubmit
? onBudgetSubmit(updatedJobs)
: saveJobs(updatedJobs).then(res => {
if (res.error) {
return res;
}
return fetchJobs(
undefined,
undefined,
true
).then(({ payload }) => {
if (isOnboarding && payload.jobs) {
return setCurrentJob(payload.jobs.first(), "add");
}
});
});
}
return Promise.resolve();
case "updatePayment": {
const { stripe, addBilling, fetchBilling, context } = props;
debugger;
return new Promise((resolve, reject) =>
stripe.createToken().then(payload => {
const { token } = payload;
/*
* Reject if no token passed and save the error
* that occurred while processing given card information
*/
if (payload.error && !token) {
return reject(payload);
}
const setBillingPromise = addBilling(token && token.id).then(() => {
debugger;
fetchBilling();
});
resolve(setBillingPromise);
})
);
}
case "payment": {
const { stripe, addBilling, fetchBilling, setBanner } = props;
return new Promise((resolve, reject) =>
/*
* Within the context of `Elements`, this call to createToken
* knows which Element to tokenize
*/
stripe.createToken().then(payload => {
const { token } = payload;
/*
* Reject if no token passed and save the error
* that occurred while processing given card information
*/
if (payload.error && !token) {
return reject(payload);
}
const setBillingPromise = addBilling(token && token.id).then(() =>
fetchBilling().then(res => {
if (res && !res.error) {
return fetchJobs(
undefined,
undefined,
true
).then(({ payload }) => {
if (payload.jobs) {
const firstJob = payload.jobs.first();
const jobs = setBudgetAndStatus(
fromJS([firstJob]),
convertToDollars(firstJob.get("budget")),
res.payload
);
return saveJobs(jobs.toJS()).then(res => {
if (res && res.error) {
return res;
}
return fetchJobs(undefined, undefined, true).then(
setBanner("success", null, true)
);
});
}
});
}
})
);
// Credit card accepted and token created successfully
resolve(setBillingPromise);
})
);
}
default:
return Promise.resolve();
}
});
export const form = {
get: (name, props, context) => content[name](props, context),
submit: (steps, props) => submit(steps, props),
disableSubmit: (formName, valid, stripeFieldsValid, budgetValues) => {
/*
* TODO: Removed (!budgetValues.endDate) from validation, but should add back in if
* we decide we need this property in the future
*/
if (formName === "payment") {
return !valid[formName] || !stripeFieldsValid;
}
if (formName === "budget") {
return (
!valid[formName] || !budgetValues || budgetValues.budget < minimumBudget
);
}
return !valid[formName];
}
};
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { isValid } from "redux-form";
import { injectStripe } from "react-stripe-elements";
import { addBilling, setDiscoveryFormError } from "../../actions/discovery";
import { form } from "../../helpers/discovery";
import Button from "../../components/Button";
import Link from "../../components/Link";
import s from "./UpdateCreditCard.css";
import c from "./content.json";
export class UpdateCreditCard extends Component {
constructor(props) {
super(props);
this.state = {
showingForm: false,
stripeFieldsValid: false,
isUpdate: true,
paymentFormName: "payment"
};
}
handleSubmit() {
const { stripe, addBilling, fetchBilling } = this.props;
const context = this;
Promise.all(
form.submit(["updatePayment"], {
stripe,
addBilling,
fetchBilling,
context
})
).then(res => {
debugger;
});
}
componentDidMount() {
this.setState({ user: this.props.user });
}
render() {
const { showingForm, stripeFieldsValid, paymentFormName } = this.state;
const { valid, user, billing } = this.props;
return (
<div>
{showingForm ? (
<div>
{form.get(paymentFormName, this.props, this)}
<div className={s.paymentButtonWrapper}>
<Button
primary
className={s.updateCard}
disabled={form.disableSubmit(
paymentFormName,
valid,
stripeFieldsValid
)}
onClick={() => this.handleSubmit()}
>
{c.updateCard}
</Button>
<Link onClick={() => this.setState({ showingForm: false })}>
{c.cancel}
</Link>
</div>
</div>
) : (
<div>
<p className={s.userName}>{user.get("full_name")}</p>
<p>
{billing
? `**** **** **** ${this.props.billing.payment_method.label}`
: c.cardError}
</p>
<div className={s.buttonWrapper}>
<Button
primary
className={s.changeCard}
onClick={() => this.setState({ showingForm: true })}
>
{c.changeCard}
</Button>
</div>
</div>
)}
</div>
);
}
}
UpdateCreditCard.propTypes = {
user: PropTypes.object,
billing: PropTypes.object
};
export const mapStateToProps = state => ({
valid: {
payment: isValid("payment")(state)
}
});
export const mapDispatchToProps = dispatch => ({
addBilling: token => dispatch(addBilling(token)),
handleFormError: error => dispatch(setDiscoveryFormError(error))
});
export default connect(mapStateToProps, mapDispatchToProps)(
injectStripe(UpdateCreditCard)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment