Skip to content

Commit

Permalink
Introduced shop, the superstore
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Feb 16, 2017
1 parent 3b1d5c5 commit 8a6a6e1
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 96 deletions.
10 changes: 5 additions & 5 deletions src/components/App.js
Expand Up @@ -8,16 +8,16 @@ import BookDetails from "./BookDetails"
import Cart from "./Cart"


const App = inject("viewStore")(observer(({ viewStore }) => (
const App = inject("shop")(observer(({ shop }) => (
<div className="App">
<AppHeader />
<AppMenu>
<AppMenuItem onClick={viewStore.openBooksPage}>Available books</AppMenuItem>
<AppMenuItem onClick={viewStore.openCartPage}>Your cart</AppMenuItem>
<AppMenuItem onClick={shop.view.openBooksPage}>Available books</AppMenuItem>
<AppMenuItem onClick={shop.view.openCartPage}>Your cart</AppMenuItem>
</AppMenu>
{viewStore.isLoading
{shop.isLoading
? <h1>Loading...</h1>
: renderPage(viewStore)
: renderPage(shop.view)
}
</div>
)))
Expand Down
10 changes: 3 additions & 7 deletions src/components/App.test.js
Expand Up @@ -3,18 +3,14 @@ import ReactDOM from 'react-dom'
import App from './App'
import { Provider } from 'mobx-react'

import BookStore from "../stores/BookStore"
import CartStore from '../stores/CartStore'
import ViewStore from '../stores/ViewStore'
import ShopStore from "../stores/ShopStore"

it('renders without crashing', () => {
const bookStore = new BookStore(() => Promise.reject("Not supported"))
const cartStore = new CartStore(bookStore)
const viewStore = new ViewStore(bookStore)
const shop = new ShopStore(() => Promise.resolve([]));

const div = document.createElement('div')
ReactDOM.render(
<Provider bookStore={bookStore} cartStore={cartStore} viewStore={viewStore}>
<Provider shop={shop}>
<App />
</Provider>,
div)
Expand Down
4 changes: 2 additions & 2 deletions src/components/BookDetails.js
@@ -1,14 +1,14 @@
import React from 'react'
import { inject, observer } from 'mobx-react'

const BookDetails = inject("cartStore")(observer(({book, cartStore}) => (
const BookDetails = inject("shop")(observer(({book, shop}) => (
<section className="Page-book">
<h2>{book.name}</h2>
<p><i>By: {book.author}</i></p>
<p>Price: ${book.price}</p>
<button
onClick={() => {
cartStore.addBook(book)
shop.cart.addBook(book)
alert("Added to cart")
}}
>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Books.js
@@ -1,11 +1,11 @@
import React from 'react'
import { observer, inject } from 'mobx-react'

const Books = inject("bookStore")(observer(({bookStore}) => (
const Books = inject("shop")(observer(({shop}) => (
<section className="Page-books">
<h1>Available books</h1>
<ol>
{bookStore.sortedAvailableBooks.map(book =>
{shop.sortedAvailableBooks.map(book =>
<BookEntry
key={book.id}
book={book}
Expand All @@ -15,13 +15,13 @@ const Books = inject("bookStore")(observer(({bookStore}) => (
</section>
)))

const BookEntry = inject("viewStore")(observer(({book, viewStore}) => (
const BookEntry = inject("shop")(observer(({book, shop}) => (
<li>
<a
href={`/book/${book.id}`}
onClick={(e) => {
e.preventDefault();
viewStore.openBookPage(book);
shop.view.openBookPage(book);
return false;
}}
>{book.name}</a>
Expand Down
24 changes: 12 additions & 12 deletions src/components/Cart.js
Expand Up @@ -2,22 +2,22 @@ import React from 'react'
import { observer, inject } from 'mobx-react'
import './Cart.css'

const Cart = inject("cartStore")(observer(({cartStore}) => (
const Cart = inject("shop")(observer(({ shop: { cart }}) => (
<section className="Page-cart">
<h2>Your cart</h2>
<section className="Page-cart-items">
{cartStore.entries.map(entry =>
{cart.entries.map(entry =>
<CartEntry key={entry.book.id} entry={entry} />
)}
</section>
<p>Subtotal: {cartStore.subTotal}</p>
{cartStore.hasDiscount && <p><i>Large order discount: {cartStore.discount}</i></p>}
<p><b>Total: {cartStore.total}</b></p>
<p>Subtotal: {cart.subTotal}</p>
{cart.hasDiscount && <p><i>Large order discount: {cart.discount}</i></p>}
<p><b>Total: {cart.total}</b></p>
<button
disabled={!cartStore.canCheckout}
disabled={!cart.canCheckout}
onClick={() => {
const total = cartStore.total
cartStore.clear()
const total = cart.total
cart.clear()
alert(`Bought books for ${total} € !`)
}}
>
Expand All @@ -26,10 +26,10 @@ const Cart = inject("cartStore")(observer(({cartStore}) => (
</section>
)))

const CartEntry = inject("viewStore")(observer(({viewStore, entry}) => (
const CartEntry = inject("shop")(observer(({shop, entry}) => (
<div className="Page-cart-item">
<p>
<a href={`/book/${entry.book.id}`} onClick={onEntryClick.bind(entry, viewStore)}>
<a href={`/book/${entry.book.id}`} onClick={onEntryClick.bind(entry, shop)}>
{entry.book.name}
</a>
</p>
Expand All @@ -43,8 +43,8 @@ const CartEntry = inject("viewStore")(observer(({viewStore, entry}) => (
</div>
)))

function onEntryClick(viewStore, e) {
viewStore.openBookPage(this.book);
function onEntryClick(shop, e) {
shop.view.openBookPage(this.book);
e.preventDefault();
return false;
}
Expand Down
20 changes: 7 additions & 13 deletions src/index.js
Expand Up @@ -7,36 +7,30 @@ import createRouter from './utils/router'
import App from './components/App'
import './index.css'

import BookStore from './stores/BookStore'
import CartStore from './stores/CartStore'
import ViewStore from './stores/ViewStore'
import ShopStore from './stores/ShopStore'

const fetcher = url => window.fetch(url).then(response => response.json())
const bookStore = new BookStore(fetcher)
const cartStore = new CartStore(bookStore)
const viewStore = new ViewStore(bookStore)
const shop = new ShopStore(fetcher)

ReactDOM.render(
<Provider bookStore={bookStore} cartStore={cartStore} viewStore={viewStore}>
<Provider shop={shop}>
<App />
</Provider>,
document.getElementById('root')
)

bookStore.loadBooks()

reaction(
() => viewStore.currentUrl,
() => shop.view.currentUrl,
(path) => {
if (window.location.pathname !== path)
window.history.pushState(null, null, path)
}
)

const router = createRouter({
"/book/:bookId": ({bookId}) => viewStore.openBookPageById(bookId),
"/cart": viewStore.openCartPage,
"/": viewStore.openBooksPage
"/book/:bookId": ({bookId}) => shop.view.openBookPageById(bookId),
"/cart": shop.view.openCartPage,
"/": shop.view.openBooksPage
})

window.onpopstate = function historyChange(ev) {
Expand Down
8 changes: 4 additions & 4 deletions src/stores/BookStore.js
Expand Up @@ -12,10 +12,10 @@ class Book {
export default class BookStore {
@observable isLoading = true
books = observable.map()
fetch
shop

constructor(fetch) {
this.fetch = fetch;
constructor(shop) {
this.shop = shop
setInterval(this.loadBooks, 5000)
}

Expand All @@ -32,7 +32,7 @@ export default class BookStore {
}

@action.bound loadBooks() {
this.fetch("/books.json")
this.shop.fetch("/books.json")
.then(json => {
this.updateBooks(json)
this.isLoading = false
Expand Down
8 changes: 3 additions & 5 deletions src/stores/BookStore.test.js
@@ -1,12 +1,11 @@
import * as fs from "fs"
import {when} from "mobx"
import BookStore from './BookStore'
import ShopStore from './ShopStore'

const bookFetcher = () => Promise.resolve(JSON.parse(fs.readFileSync("./public/books.json")))

it('bookstore fetches data', (done) => {
const store = new BookStore(bookFetcher)
store.loadBooks()
const store = new ShopStore(bookFetcher)
when(
() => store.isLoading === false,
() => {
Expand All @@ -18,8 +17,7 @@ it('bookstore fetches data', (done) => {
})

it('bookstore sorts data', (done) => {
const store = new BookStore(bookFetcher)
store.loadBooks()
const store = new ShopStore(bookFetcher)
when(
() => store.isLoading === false,
() => {
Expand Down
10 changes: 5 additions & 5 deletions src/stores/CartStore.js
Expand Up @@ -25,15 +25,15 @@ class CartEntry {
}

export default class CartStore {
bookStore
shop
@observable entries = []

constructor(bookStore) {
this.bookStore = bookStore
constructor(shop) {
this.shop = shop

if (typeof window.localStorage !== "undefined") {
when(
() => !bookStore.isLoading,
() => !shop.isLoading,
() => {
this.readFromLocalStorage();
reaction(
Expand Down Expand Up @@ -87,7 +87,7 @@ export default class CartStore {
@action readFromLocalStorage() {
const data = JSON.parse(window.localStorage.getItem('cart') || '[]')
data.forEach(json => {
const book = this.bookStore.books.get(json.book)
const book = this.shop.books.get(json.book)
if (book)
this.addBook(book, json.quantity)
});
Expand Down
64 changes: 30 additions & 34 deletions src/stores/CartStore.test.js
@@ -1,64 +1,60 @@
import * as fs from "fs"
import { when } from "mobx"
import BookStore from './BookStore'
import CartStore from './CartStore'
import {when} from "mobx"
import ShopStore from './ShopStore'

const bookFetcher = () => Promise.resolve(JSON.parse(fs.readFileSync("./public/books.json")))

it('cart store can add new entries', () => {
const bookStore = new BookStore(bookFetcher)
bookStore.updateBooks([{
const shop = new ShopStore(bookFetcher)
shop.bookStore.updateBooks([{
id: 1,
price: 3
}])

const cart = new CartStore(bookStore)
cart.addBook(bookStore.books.get(1))
cart.addBook(bookStore.books.get(1))
shop.cart.addBook(shop.books.get(1))
shop.cart.addBook(shop.books.get(1))

expect(cart.subTotal).toBe(6)
expect(cart.total).toBe(6)
expect(shop.cart.subTotal).toBe(6)
expect(shop.cart.total).toBe(6)

cart.entries[0].quantity = 100
expect(cart.subTotal).toBe(300)
expect(cart.total).toBe(270)
shop.cart.entries[0].quantity = 100
expect(shop.cart.subTotal).toBe(300)
expect(shop.cart.total).toBe(270)
})

it('cart store can clear entries', () => {
const bookStore = new BookStore(bookFetcher)
bookStore.updateBooks([{
const shop = new ShopStore(bookFetcher)
shop.bookStore.updateBooks([{
id: 1,
price: 3
}])

const cart = new CartStore(bookStore)
cart.addBook(bookStore.books.get(1))
shop.cart.addBook(shop.books.get(1))

expect(cart.total).toBe(3)
expect(cart.canCheckout).toBe(true)
expect(shop.cart.total).toBe(3)
expect(shop.cart.canCheckout).toBe(true)

cart.clear()
expect(cart.total).toBe(0)
expect(cart.canCheckout).toBe(false)
shop.cart.clear()
expect(shop.cart.total).toBe(0)
expect(shop.cart.canCheckout).toBe(false)
})

it('cart store can clear entries', () => {
const bookStore = new BookStore(bookFetcher)
bookStore.updateBooks([{
const shop = new ShopStore(bookFetcher)
shop.bookStore.updateBooks([{
id: 1,
price: 3
}])

const cart = new CartStore(bookStore)
cart.addBook(bookStore.books.get(1))
shop.cart.addBook(shop.books.get(1))

expect(cart.total).toBe(3)
expect(cart.canCheckout).toBe(true)
expect(shop.cart.total).toBe(3)
expect(shop.cart.canCheckout).toBe(true)

bookStore.updateBooks([])
expect(cart.total).toBe(3)
expect(cart.canCheckout).toBe(false)
expect(bookStore.books.get(1).isAvailable).toBe(false)
expect(bookStore.books.size).toBe(1)
expect(bookStore.sortedAvailableBooks.length).toBe(0)
shop.bookStore.updateBooks([])
expect(shop.cart.total).toBe(3)
expect(shop.cart.canCheckout).toBe(false)
expect(shop.books.get(1).isAvailable).toBe(false)
expect(shop.books.size).toBe(1)
expect(shop.sortedAvailableBooks.length).toBe(0)
})
30 changes: 30 additions & 0 deletions src/stores/ShopStore.js
@@ -0,0 +1,30 @@
import BookStore from './BookStore'
import CartStore from './CartStore'
import ViewStore from './ViewStore'

export default class ShopStore {
fetch
bookStore
cartStore
viewStore

constructor(fetcher) {
this.fetch = fetcher
this.bookStore = new BookStore(this)
this.cart = new CartStore(this)
this.view = new ViewStore(this)
this.bookStore.loadBooks()
}

get isLoading() {
return this.bookStore.isLoading
}

get books() {
return this.bookStore.books
}

get sortedAvailableBooks() {
return this.bookStore.sortedAvailableBooks
}
}

0 comments on commit 8a6a6e1

Please sign in to comment.