Skip to content

Commit

Permalink
Styling for checkout page (#265)
Browse files Browse the repository at this point in the history
* Styling for checkout page

* Lint

* Remove React.Fragment

* Courses => Course

* flex-row

* Align course image with order summary

* Border for validation errors, rearrange some CSS

* Shrink left column a little, expand right column. Alignment for apply button and selects

* Tests

* Bigger font for description, remove .only

* Fix dashes
  • Loading branch information
George Schneeloch committed May 15, 2019
1 parent 984618b commit 8fc5b3d
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 78 deletions.
127 changes: 86 additions & 41 deletions static/js/containers/pages/CheckoutPage.js
Expand Up @@ -9,7 +9,8 @@ import queries from "../../lib/queries"
import {
calculateDiscount,
calculatePrice,
formatPrice
formatPrice,
formatRunTitle
} from "../../lib/ecommerce"
import { createCyberSourceForm } from "../../lib/form"

Expand Down Expand Up @@ -157,12 +158,11 @@ export class CheckoutPage extends React.Component<Props, State> {
if (item.type === "program") {
return (
<React.Fragment>
<div className="row">
You are about to purchase the following program
</div>
{item.courses.map(course => (
<div className="row course-row" key={course.id}>
<img src={course.thumbnail_url} alt={course.title} />
<div className="flex-row item-row" key={course.id}>
<div className="flex-row item-column">
<img src={course.thumbnail_url} alt={course.title} />
</div>
<div className="title-column">
<div className="title">{course.title}</div>
<select
Expand All @@ -175,7 +175,7 @@ export class CheckoutPage extends React.Component<Props, State> {
</option>
{course.courseruns.map(run => (
<option value={run.id} key={run.id}>
{run.title}
{formatRunTitle(run)}
</option>
))}
</select>
Expand All @@ -186,17 +186,14 @@ export class CheckoutPage extends React.Component<Props, State> {
)
} else {
return (
<React.Fragment>
<div className="row">
You are about to purchase the following course run
</div>
<div className="row course-row">
<div className="flex-row item-row">
<div className="flex-row item-column">
<img src={item.thumbnail_url} alt={item.description} />
<div className="title-column">
<div className="title">{item.description}</div>
</div>
</div>
</React.Fragment>
<div className="title-column">
<div className="title">{item.description}</div>
</div>
</div>
)
}
}
Expand All @@ -219,34 +216,82 @@ export class CheckoutPage extends React.Component<Props, State> {
)

return (
<div className="checkout-page">
{this.renderBasketItem(item)}
<div className="row price-row">Price {formatPrice(item.price)}</div>
{coupon ? (
<div className="row discount-row">
Discount applied {formatPrice(calculateDiscount(item, coupon))}
<div className="checkout-page container">
<div className="row header">
<div className="col-12">
<div className="page-title">Checkout</div>
<div className="purchase-text">
You are about to purchase the following:
</div>
<div className="item-type">
{item.type === "program" ? "Program" : "Course"}
</div>
<hr />
{item.type === "program" ? (
<span className="description">{item.description}</span>
) : null}
</div>
) : null}
<div className="row">Coupon (optional)</div>
<div className="row coupon-code-row">
<form onSubmit={this.submitCoupon}>
<input
type="text"
value={
(couponCode !== null ? couponCode : coupon && coupon.code) || ""
}
onChange={this.updateCouponCode}
/>
<button type="submit">Update</button>
</form>
</div>
<div className="row total-row">
Total {formatPrice(calculatePrice(item, coupon))}
<div className="row">
<div className="col-lg-7">
{this.renderBasketItem(item)}
<div className="enrollment-input">
<div className="enrollment-row">
Enrollment / Promotional Code
</div>
<form onSubmit={this.submitCoupon}>
<div className="flex-row coupon-code-row">
<input
type="text"
className={errors ? "error-border" : ""}
value={
(couponCode !== null
? couponCode
: coupon && coupon.code) || ""
}
onChange={this.updateCouponCode}
/>
<button
className="apply-button"
type="button"
onClick={this.submitCoupon}
>
Apply
</button>
</div>
{errors ? <div className="error">Error: {errors}</div> : null}
</form>
</div>
</div>
<div className="col-lg-5 order-summary-container">
<div className="order-summary">
<div className="title">Order Summary</div>
<div className="flex-row price-row">
<span>Price:</span>
<span>{formatPrice(item.price)}</span>
</div>
{coupon ? (
<div className="flex-row discount-row">
<span>Discount:</span>
<span>{formatPrice(calculateDiscount(item, coupon))}</span>
</div>
) : null}
<div className="bar" />
<div className="flex-row total-row">
<span>Total:</span>
<span>{formatPrice(calculatePrice(item, coupon))}</span>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-7" />
<div className="col-lg-5">
<button className="checkout-button" onClick={this.submit}>
Place your order
</button>
</div>
</div>
{errors ? <div className="error">Error: {errors}</div> : null}
<button className="checkout" onClick={this.submit}>
Place your order
</button>
</div>
)
}
Expand Down
52 changes: 23 additions & 29 deletions static/js/containers/pages/CheckoutPage_test.js
Expand Up @@ -15,7 +15,8 @@ import {
import {
calculateDiscount,
calculatePrice,
formatPrice
formatPrice,
formatRunTitle
} from "../../lib/ecommerce"
import { assertRaises } from "../../lib/util"
import { PRODUCT_TYPE_COURSERUN, PRODUCT_TYPE_PROGRAM } from "../../constants"
Expand Down Expand Up @@ -56,40 +57,35 @@ describe("CheckoutPage", () => {
coupon.targets = [-123]
}
const { inner } = await renderPage()

assert.equal(inner.find(".item-type").text(), "Program")
assert.equal(
inner
.find(".row")
.first()
.text(),
"You are about to purchase the following program"
inner.find(".header .description").text(),
basketItem.description
)
assert.equal(inner.find(".course-row").length, basketItem.courses.length)
assert.equal(inner.find(".item-row").length, basketItem.courses.length)
basketItem.courses.forEach((course, i) => {
const courseRow = inner.find(".course-row").at(i)
const courseRow = inner.find(".item-row").at(i)
assert.equal(courseRow.find("img").prop("src"), course.thumbnail_url)
assert.equal(courseRow.find("img").prop("alt"), course.title)
assert.equal(courseRow.find(".title").text(), course.title)
})
assert.equal(
inner.find(".price-row").text(),
`Price ${formatPrice(basketItem.price)}`
`Price:${formatPrice(basketItem.price)}`
)

if (hasCoupon) {
assert.equal(
inner.find(".discount-row").text(),
`Discount applied ${formatPrice(
calculateDiscount(basketItem, coupon)
)}`
`Discount:${formatPrice(calculateDiscount(basketItem, coupon))}`
)
} else {
assert.isFalse(inner.find(".discount-row").exists())
}

assert.equal(
inner.find(".total-row").text(),
`Total ${formatPrice(calculatePrice(basketItem, coupon))}`
`Total:${formatPrice(calculatePrice(basketItem, coupon))}`
)
})
})
Expand All @@ -100,18 +96,11 @@ describe("CheckoutPage", () => {

const { inner } = await renderPage()

assert.equal(
inner
.find(".row")
.first()
.text(),
"You are about to purchase the following course run"
)
assert.equal(inner.find(".course-row").length, 1)

assert.equal(inner.find(".item-type").text(), "Course")
assert.equal(inner.find(".item-row").length, 1)
assert.equal(inner.find("img").prop("src"), basketItem.thumbnail_url)
assert.equal(inner.find("img").prop("alt"), basketItem.description)
assert.equal(inner.find(".title").text(), basketItem.description)
assert.equal(inner.find(".item-row .title").text(), basketItem.description)
})

it("displays the coupon code", async () => {
Expand Down Expand Up @@ -178,6 +167,11 @@ describe("CheckoutPage", () => {
})

assert.equal(inner.state().errors, errors)
assert.equal(
inner.find(".enrollment-input .error").text(),
"Error: Unknown error"
)
assert.isTrue(inner.find(".enrollment-input input.error-border").exists())
})

it("checks out", async () => {
Expand All @@ -198,7 +192,7 @@ describe("CheckoutPage", () => {
const createFormStub = helper.sandbox
.stub(formFuncs, "createCyberSourceForm")
.returns(form)
await inner.find("button.checkout").prop("onClick")()
await inner.find(".checkout-button").prop("onClick")()
sinon.assert.calledWith(createFormStub, url, payload)
sinon.assert.calledWith(submitStub)
sinon.assert.calledWith(
Expand Down Expand Up @@ -249,7 +243,7 @@ describe("CheckoutPage", () => {
const form = document.createElement("form")
// $FlowFixMe: need to overwrite this function to mock it
form.submit = submitStub
await inner.find("button.checkout").prop("onClick")()
await inner.find(".checkout-button").prop("onClick")()

const basketItem = basket.items[0]
sinon.assert.calledWith(helper.handleRequestStub, "/api/basket/", "PATCH", {
Expand Down Expand Up @@ -287,7 +281,7 @@ describe("CheckoutPage", () => {
form.submit = submitStub

await assertRaises(async () => {
await inner.find("button.checkout").prop("onClick")()
await inner.find(".checkout-button").prop("onClick")()
}, "Received error from request")
assert.deepEqual(inner.state().errors, errors)
})
Expand All @@ -307,7 +301,7 @@ describe("CheckoutPage", () => {
// $FlowFixMe: need to overwrite this function to mock it
form.submit = submitStub
await assertRaises(async () => {
await inner.find("button.checkout").prop("onClick")()
await inner.find(".checkout-button").prop("onClick")()
}, "Received error from request")
})

Expand Down Expand Up @@ -386,7 +380,7 @@ describe("CheckoutPage", () => {
runs.forEach((run, j) => {
const runOption = select.find("option").at(j + 1)
assert.equal(runOption.prop("value"), run.id)
assert.equal(runOption.text(), run.title)
assert.equal(runOption.text(), formatRunTitle(run))
})
})
})
Expand Down
8 changes: 8 additions & 0 deletions static/js/lib/ecommerce.js
Expand Up @@ -2,6 +2,7 @@
import Decimal from "decimal.js-light"
import * as R from "ramda"
import { equals } from "ramda"
import moment from "moment"

import type {
BasketItem,
Expand All @@ -15,6 +16,7 @@ import {
PRODUCT_TYPE_COURSERUN,
PRODUCT_TYPE_PROGRAM
} from "../constants"
import type { CourseRun } from "../flow/courseTypes"

export const calculateDiscount = (
item: BasketItem,
Expand Down Expand Up @@ -49,6 +51,12 @@ export const formatPrice = (price: ?string | number | Decimal): string => {
}
}

const formatDateForRun = (dateString: ?string) =>
dateString ? moment(dateString).format("ll") : "?"

export const formatRunTitle = (run: CourseRun) =>
`${formatDateForRun(run.start_date)} - ${formatDateForRun(run.end_date)}`

export const isPromo = equals(COUPON_TYPE_PROMO)

export const createProductMap = (
Expand Down
30 changes: 29 additions & 1 deletion static/js/lib/ecommerce_test.js
@@ -1,19 +1,26 @@
// @flow
import { assert } from "chai"
import Decimal from "decimal.js-light"
import moment from "moment"

import {
makeItem,
makeCouponSelection,
makeBulkCouponPayment,
makeProduct
} from "../factories/ecommerce"
import { calculatePrice, formatPrice, createProductMap } from "./ecommerce"
import {
calculatePrice,
formatPrice,
formatRunTitle,
createProductMap
} from "./ecommerce"
import {
PRODUCT_TYPE_COURSE,
PRODUCT_TYPE_COURSERUN,
PRODUCT_TYPE_PROGRAM
} from "../constants"
import { makeCourseRun } from "../factories/course"

describe("ecommerce", () => {
describe("calculatePrice", () => {
Expand Down Expand Up @@ -80,4 +87,25 @@ describe("ecommerce", () => {
})
})
})

describe("formatRunTitle", () => {
it("creates text based on the run's dates", () => {
const run = makeCourseRun()
assert.equal(
formatRunTitle(run),
`${moment(run.start_date).format("ll")} - ${moment(run.end_date).format(
"ll"
)}`
)
})

it("swaps out missing pieces with a question mark", () => {
const run = {
...makeCourseRun(),
start_date: null,
end_date: null
}
assert.equal(formatRunTitle(run), "? - ?")
})
})
})

0 comments on commit 8fc5b3d

Please sign in to comment.