Created
March 14, 2018 05:47
-
-
Save jacobmoyle/7d4fa727f14006f7453640c98b564dd9 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
| 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 | |
| } | |
| } | |
| ] | |
| } | |
| }); |
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-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]; | |
| } | |
| }; |
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
| 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