diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-03-22 16:29:31 -0400 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-03-22 16:29:31 -0400 |
| commit | a6af043686e4375d7944e91cae3d5b63c59edab0 (patch) | |
| tree | f814d412b14a2a47f56c933ce38f458055f2f83b /frontend/components | |
| parent | 93495ce146c4e81ecfab3a0bca99460e75a759da (diff) | |
Migrate to render props
Diffstat (limited to 'frontend/components')
26 files changed, 726 insertions, 719 deletions
diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js index 22cfe02..4757296 100644 --- a/frontend/components/AddToCart.js +++ b/frontend/components/AddToCart.js @@ -1,36 +1,34 @@ import { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; -import styled from 'styled-components'; +import { Mutation } from 'react-apollo'; import PropTypes from 'prop-types'; -import { removeFromCartEnhancer, userEnhancer, addToCartEnhancer, singleItemEnhancer } from '../enhancers/enhancers'; +import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY } from '../queries/index'; class AddToCart extends Component { static propTypes = { - currentUser: PropTypes.object.isRequired, + id: PropTypes.string.isRequired, }; - componentDidMount() { - this.props.currentUser.refetch(); - } - - handleAddToCart = async () => { - const res = await this.props.addToCart({ - variables: { - id: this.props.id, - }, - }); + update = (proxy, payload) => { + const newCartItem = payload.data.addToCart; + const data = proxy.readQuery({ query: CURRENT_USER_QUERY }); + const existingIndex = data.me.cart.findIndex(cartItem => cartItem.id === newCartItem.id); + if (existingIndex >= 0) { + // already in cache, just replace it + data.me.cart = [...data.me.cart.slice(0, existingIndex), newCartItem, ...data.me.cart.slice(existingIndex + 1)]; + } else { + data.me.cart = [...data.me.cart, newCartItem]; + } + proxy.writeQuery({ query: CURRENT_USER_QUERY, data }); }; render() { - return <button onClick={this.handleAddToCart}>๐ Add To Cart</button>; + const { id } = this.props; + return ( + <Mutation mutation={ADD_TO_CART_MUTATION} variables={{ id }} update={this.update}> + {addToCart => <button onClick={addToCart}>๐ Add To Cart</button>} + </Mutation> + ); } } -AddToCart.propTypes = { - currentUser: PropTypes.object.isRequired, - addToCart: PropTypes.func.isRequired, - id: PropTypes.string.isRequired, -}; - -export default compose(userEnhancer, addToCartEnhancer)(AddToCart); -export { AddToCart }; +export default AddToCart; diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js index 26a11d4..fee757e 100644 --- a/frontend/components/Cart.js +++ b/frontend/components/Cart.js @@ -1,12 +1,11 @@ import React from 'react'; -import { compose } from 'react-apollo'; +import { Query } from 'react-apollo'; import styled from 'styled-components'; -import PropTypes from 'prop-types'; -import { userEnhancer } from '../enhancers/enhancers'; import TakeMyMoney from './TakeMyMoney'; import formatMoney from '../lib/formatMoney'; import { UIContext } from './UIContext'; import CartItem from './CartItem'; +import { CURRENT_USER_QUERY } from '../queries/index'; const CartStyles = styled.div` padding: 20px; @@ -73,43 +72,42 @@ const CloseButton = styled.button` right: 0; `; -const Cart = props => { - const { me } = props.currentUser; - if (!me) return null; - const itemCount = me.cart.length; - const totalPrice = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity * cartItem.item.price, 0); +function calcTotalPrice(cart) { + return cart.reduce((tally, cartItem) => tally + cartItem.quantity * cartItem.item.price, 0); +} - return ( - <UIContext.Consumer> - {context => ( - <CartStyles open={context.state.isCartOpen}> - <header> - <CloseButton title="close" onClick={context.toggle}> - × - </CloseButton> +const Cart = props => ( + <UIContext.Consumer> + {context => ( + <Query query={CURRENT_USER_QUERY}> + {({ data: { me }, error, loading }) => { + if (loading || error || !me) return null; + return ( + <CartStyles open={context.state.isCartOpen}> + <header> + <CloseButton title="close" onClick={context.toggle}> + × + </CloseButton> - <Supreme>{me.name}'s Cart.</Supreme> - <p> - You have {itemCount} item{itemCount === 1 ? '' : 's'} in your cart. - </p> - </header> + <Supreme>{me.name}'s Cart.</Supreme> + <p> + You have {me.cart.length} item{me.cart.length === 1 ? '' : 's'} in your cart. + </p> + </header> - <CartUl>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</CartUl> - <footer> - <p>{formatMoney(totalPrice)}</p> - <TakeMyMoney> - <button>Checkout</button> - </TakeMyMoney> - </footer> - </CartStyles> - )} - </UIContext.Consumer> - ); -}; + <CartUl>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</CartUl> + <footer> + <p>{formatMoney(calcTotalPrice(me.cart))}</p> + <TakeMyMoney> + <button>Checkout</button> + </TakeMyMoney> + </footer> + </CartStyles> + ); + }} + </Query> + )} + </UIContext.Consumer> +); -Cart.propTypes = { - currentUser: PropTypes.object.isRequired, -}; - -export default compose(userEnhancer)(Cart); -export { Cart }; +export default Cart; diff --git a/frontend/components/CartCount.js b/frontend/components/CartCount.js index 7949307..245b07b 100644 --- a/frontend/components/CartCount.js +++ b/frontend/components/CartCount.js @@ -50,4 +50,3 @@ const CartCount = ({ count }) => ( ); export default CartCount; -export { CartCount }; diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js index 74a6cee..d166c81 100644 --- a/frontend/components/CreateItem.js +++ b/frontend/components/CreateItem.js @@ -1,36 +1,24 @@ import React, { Component } from 'react'; -import { graphql, gql } from 'react-apollo'; +import { Mutation } from 'react-apollo'; import { CREATE_ITEM_MUTATION } from '../queries'; -import ErrorMessage from './ErrorMessage'; +import Error from './ErrorMessage'; import Form from './styles/Form'; import PropTypes from 'prop-types'; +import Router from 'next/router'; class CreateItem extends Component { - static propTypes = { - createItemMutation: PropTypes.func.isRequired, - }; - state = { - item: { - title: '', - description: '', - image: '', - largeImage: '', - price: 0, - }, - loading: false, - error: { - message: null, - }, + title: '', + description: '', + image: '', + largeImage: '', + price: 0, }; handleChange = e => { const { name, value, type } = e.target; - const updatedItem = { - ...this.state.item, - [name]: type === 'number' ? parseFloat(value) : value, - }; - this.setState({ item: updatedItem }); + const val = type === 'number' ? parseFloat(value) : value; + this.setState({ [name]: val }); }; uploadFile = async e => { @@ -46,81 +34,73 @@ class CreateItem extends Component { body: data, }); const file = await res.json(); - this.setState({ image: file.secure_url, largeImage: file.eager[0].secure_url, loading: false }); - }; - - createItem = async e => { - e.preventDefault(); - // TODO: handle any errors - // turn loading on - this.setState({ loading: true }); - try { - const res = await this.props.createItemMutation({ - // pass in those variables from state - variables: { - ...this.state.item, - }, - }); - } catch (error) { - this.setState({ error }); - } - this.setState({ loading: false }); + this.setState({ + image: file.secure_url, + largeImage: file.eager[0].secure_url, + loading: false, + }); }; render() { return ( - <div> - <Form onSubmit={this.createItem}> - {this.state.loading ? 'LOADING...' : null} - <ErrorMessage error={this.state.error} onButtonClick={() => this.setState({ error: {} })} /> - <p> - Image - <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" /> - {this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null} - </p> - <p> - Title - <input - value={this.state.title} - onChange={this.handleChange} - type="text" - name="title" - id="title" - placeholder="Title" - /> - </p> - <label> - Price - <input - type="number" - id="price" - name="price" - min="0" - value={this.state.price} - onChange={this.handleChange} - /> - </label> - <textarea - id="description" - name="description" - value={this.state.description} - onChange={this.handleChange} - placeholder="The desc for this item" - /> - <button disabled={this.state.loading} type="submit"> - Submit - </button> - </Form> - </div> + <Mutation mutation={CREATE_ITEM_MUTATION} variables={this.state}> + {(createItem, { loading, error }) => ( + <Form + onSubmit={async e => { + e.preventDefault(); + const { data: { createItem: item } } = await createItem(); + Router.push({ + pathname: `/item`, + query: { id: item.id }, + }); + }} + > + <h2>Sell an Item.</h2> + <Error error={error} /> + <fieldset disabled={loading} aria-busy={loading}> + <label> + Image + <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" /> + {this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null} + </label> + <label> + Title + <input + value={this.state.title} + onChange={this.handleChange} + type="text" + name="title" + id="title" + placeholder="Title" + /> + </label> + <label> + Price + <input + type="number" + id="price" + name="price" + min="0" + value={this.state.price} + onChange={this.handleChange} + /> + </label> + <textarea + id="description" + name="description" + value={this.state.description} + onChange={this.handleChange} + placeholder="The desc for this item" + /> + <button disabled={this.state.loading} type="submit"> + Submit + </button> + </fieldset> + </Form> + )} + </Mutation> ); } } -export default graphql(CREATE_ITEM_MUTATION, { - name: 'createItemMutation', - options: { - refetchQueries: ['AllItemsQuery'], - }, -})(CreateItem); - -export { CreateItem }; +export default CreateItem; diff --git a/frontend/components/DeleteItem.js b/frontend/components/DeleteItem.js new file mode 100644 index 0000000..30b2bf7 --- /dev/null +++ b/frontend/components/DeleteItem.js @@ -0,0 +1,39 @@ +import React from 'react'; +import { Mutation } from 'react-apollo'; +import PropTypes from 'prop-types'; +import { REMOVE_ITEM_MUTATION, ALL_ITEMS_QUERY } from '../queries/index'; + +class DeleteItem extends React.Component { + static propTypes = { + id: PropTypes.string.isRequired, + }; + + update = (proxy, payload) => { + const deletedItem = payload.data.deleteItem; + const data = proxy.readQuery({ query: ALL_ITEMS_QUERY }); + // filter this one out + data.items = data.items.filter(item => item.id !== deletedItem.id); + // write the data back to the proxy + proxy.writeQuery({ query: ALL_ITEMS_QUERY, data }); + }; + + render() { + return ( + <Mutation mutation={REMOVE_ITEM_MUTATION} variables={{ id: this.props.id }} update={this.update}> + {removeItem => ( + <button + onClick={() => { + if (confirm('Are you sure you want to delete this item?')) { + removeItem(); + } + }} + > + × Delete item + </button> + )} + </Mutation> + ); + } +} + +export default DeleteItem; diff --git a/frontend/components/EditUser.js b/frontend/components/EditUser.js index b885fe6..a7a8f1c 100644 --- a/frontend/components/EditUser.js +++ b/frontend/components/EditUser.js @@ -1,59 +1,64 @@ import React from 'react'; -import { compose } from 'react-apollo'; -import PropTypes from 'prop-types'; +import { Query, Mutation } from 'react-apollo'; import Form from './styles/Form'; -import { userEnhancer, updateUserEnhancer } from '../enhancers/enhancers'; +import { CURRENT_USER_QUERY, UPDATE_USER_MUTATION } from '../queries/index'; +import Error from './ErrorMessage'; class EditUser extends React.Component { - static propTypes = { - currentUser: PropTypes.object.isRequired, - updateUser: PropTypes.func.isRequired, - }; - state = { changes: {}, }; handleChange = e => { const { name, value } = e.target; - const { me } = this.props.currentUser; const changes = { ...this.state.changes, [name]: value, }; - if (value === me[e.target.name]) { - delete changes[name]; - } this.setState({ changes }); }; - handleSubmit = async e => { + handleSubmit = async (e, updateUser) => { e.preventDefault(); - await this.props.updateUser({ - variables: this.state.changes, - }); - this.props.currentUser.refetch(); + // only submit if there are real changes + if (Object.keys(this.state.changes).length === 0) return; + await updateUser(); this.setState({ changes: {} }); }; render() { - const { me } = this.props.currentUser; - if (!me) return <p>You must be logged in</p>; return ( - <Form onSubmit={this.handleSubmit}> - <label htmlFor="name"> - Name: - <input type="text" name="name" defaultValue={me.name} onChange={this.handleChange} /> - </label> - <button type="submit">Update</button> - <strong>me:</strong> - <pre>{JSON.stringify(me.name)}</pre> - <strong>Change:</strong> - <pre data-test="change">{JSON.stringify(this.state.changes)}</pre> - </Form> + <Query query={CURRENT_USER_QUERY}> + {({ data: { me } }) => { + if (!me) return <p>You must be logged in</p>; + return ( + <Mutation + mutation={UPDATE_USER_MUTATION} + refetchQueries={[{ query: CURRENT_USER_QUERY }]} + variables={this.state.changes} + > + {(updateUser, { loading, error, called, data }) => ( + <Form onSubmit={e => this.handleSubmit(e, updateUser)}> + <Error error={error} /> + <fieldset disabled={loading} aria-busy={loading}> + <label htmlFor="name"> + Name: + <input type="text" name="name" defaultValue={me.name} onChange={this.handleChange} /> + </label> + <button type="submit">Update</button> + <strong>me:</strong> + <pre>{JSON.stringify(me.name)}</pre> + <strong>Change:</strong> + <pre data-test="change">{JSON.stringify(this.state.changes)}</pre> + </fieldset> + </Form> + )} + </Mutation> + ); + }} + </Query> ); } } -export default compose(userEnhancer, updateUserEnhancer)(EditUser); -export { EditUser }; +export default EditUser; diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js index 12de51d..ed5d031 100644 --- a/frontend/components/ErrorMessage.js +++ b/frontend/components/ErrorMessage.js @@ -4,29 +4,34 @@ import React from 'react'; import PropTypes from 'prop-types'; const StyledError = styled.div` - background: pink; - border: 2px solid red; - padding: 20px; - display: flex; + padding: 2rem; + background: white; + margin: 2rem 0; + border: 1px solid rgba(0, 0, 0, 0.05); + border-left: 5px solid red; p { margin: 0; - flex: 1 0 auto; + font-weight: 100; + } + strong { + margin-right: 1rem; } `; -const DisplayError = props => { - if (!props.error || !props.error.message) return null; +const DisplayError = ({ error }) => { + if (!error || !error.message) return null; return ( <StyledError> - <p>{props.error.message}</p> - <button onClick={props.onButtonClick}>×</button> + <p> + <strong>Shoot!</strong> + {error.message} + </p> </StyledError> ); }; DisplayError.propTypes = { - error: PropTypes.object.isRequired, - onButtonClick: PropTypes.func.isRequired, + error: PropTypes.object, }; export default DisplayError; diff --git a/frontend/components/Header.js b/frontend/components/Header.js index 0b815f9..053a18b 100644 --- a/frontend/components/Header.js +++ b/frontend/components/Header.js @@ -1,6 +1,4 @@ -import { Component } from 'react'; -import { compose } from 'react-apollo'; -import { userEnhancer } from '../enhancers/enhancers'; +import React from 'react'; import NProgress from 'nprogress'; import Router from 'next/router'; import styled from 'styled-components'; @@ -60,5 +58,4 @@ const Header = props => ( </StyledHeader> ); -export default compose(userEnhancer)(Header); -// export default Header; +export default Header; diff --git a/frontend/components/Item.js b/frontend/components/Item.js index 33b3951..9bff385 100644 --- a/frontend/components/Item.js +++ b/frontend/components/Item.js @@ -1,11 +1,11 @@ import React from 'react'; import styled from 'styled-components'; -import { compose } from 'react-apollo'; import Link from 'next/link'; import Title from './styles/Title'; import AddToCart from './AddToCart'; +import DeleteItem from './DeleteItem'; import formatMoney from '../lib/formatMoney'; -import { removeItemMutation } from '../enhancers/enhancers'; +import PropTypes from 'prop-types'; const Item = styled.div` background: white; @@ -57,11 +57,10 @@ const PriceTag = styled.span` `; class ItemComponent extends React.Component { - removeItem = () => { - if (confirm('Are you sure you want to delete this item?')) { - this.props.removeItem({ variables: { id: this.props.item.id } }); - } + static propTypes = { + item: PropTypes.object.isRequired, }; + render() { const item = this.props.item; return ( @@ -92,12 +91,11 @@ class ItemComponent extends React.Component { <a>Edit โ๏ธ</a> </Link> <AddToCart id={item.id} /> - <button onClick={this.removeItem}>× Delete item</button> + <DeleteItem id={item.id} /> </div> </Item> ); } } -export { ItemComponent }; -export default compose(removeItemMutation)(ItemComponent); +export default ItemComponent; diff --git a/frontend/components/Items.js b/frontend/components/Items.js index 1b0c8bb..1f4918f 100644 --- a/frontend/components/Items.js +++ b/frontend/components/Items.js @@ -1,9 +1,20 @@ import React from 'react'; -import { compose } from 'react-apollo'; +import { Query } from 'react-apollo'; import styled from 'styled-components'; +import PropTypes from 'prop-types'; import Pagination from './Pagination'; import Item from './Item'; -import { itemEnhancer } from '../enhancers/enhancers'; +import { perPage } from '../config'; +import { ALL_ITEMS_QUERY } from '../queries/index'; + +// export const itemEnhancer = graphql(ALL_ITEMS_QUERY, { +// name: 'itemsQuery', +// options({ page }) { +// return { +// variables: , +// }; +// }, +// }); const Items = styled.div` display: grid; @@ -18,30 +29,38 @@ const Center = styled.div` `; class ItemList extends React.Component { - something() {} + static propTypes = { + page: PropTypes.number.isRequired, + }; + componentDidUpdate(lastProps) { + if (lastProps.page === this.props.page) return; + // update the query + console.log('This fires, and should update the query'); + } render() { - const { loading, error } = this.props.itemsQuery; - // 1 - if (loading) { - return <div>Loading</div>; - } - - // 2 - if (error) { - console.log(this.props.itemsQuery.error); - return <div>Error</div>; - } - // 3 - const itemsToRender = this.props.itemsQuery.items; - return ( - <Center> + <Center key={this.props.page}> <Pagination page={this.props.page} /> - <Items key={this.props.page}>{itemsToRender.map((item, i) => <Item key={item.id} item={item} />)}</Items> + <Query + query={ALL_ITEMS_QUERY} + variables={{ + skip: this.props.page * perPage - perPage, + first: perPage, + }} + > + {({ data, error, loading, variables }) => { + if (loading) return <div>Loading</div>; + if (error) return <div>Error</div>; + return ( + <Items key={this.props.page}>{data.items.map((item, i) => <Item key={item.id} item={item} />)}</Items> + ); + }} + </Query> <Pagination page={this.props.page} /> </Center> ); } } -export default compose(itemEnhancer)(ItemList); +// export default compose(itemEnhancer)(ItemList); +export default ItemList; diff --git a/frontend/components/Nav.js b/frontend/components/Nav.js index 3d7f0f6..b898fd5 100644 --- a/frontend/components/Nav.js +++ b/frontend/components/Nav.js @@ -1,9 +1,8 @@ import React, { Fragment } from 'react'; import Link from 'next/link'; import styled from 'styled-components'; -import { compose } from 'react-apollo'; -import PropTypes from 'prop-types'; -import { userEnhancer } from '../enhancers/enhancers'; +import { Query } from 'react-apollo'; +import { CURRENT_USER_QUERY } from '../queries/index'; import CartCount from './CartCount'; import Signout from './Signout'; import { UIContext } from './UIContext'; @@ -59,55 +58,51 @@ const StyledUl = styled.ul` `; class Nav extends React.Component { - static propTypes = { - currentUser: PropTypes.object.isRequired, - }; - componentDidMount() { - this.props.currentUser.refetch(); - } render() { - const { currentUser } = this.props; return ( - <StyledUl> - <Link prefetch href="/items"> - <a>Shop</a> - </Link> - <Link prefetch href="/add"> - <a>Sell</a> - </Link> - - {!currentUser.me && ( - <Link prefetch href="/signup"> - <a>Sign In</a> - </Link> - )} - - {currentUser.me && ( - <Fragment> - <Link href="/orders"> - <a>Orders</a> + <Query query={CURRENT_USER_QUERY}> + {({ data: { me } }) => ( + <StyledUl> + <Link prefetch href="/items"> + <a>Shop</a> </Link> - <Link href="/me"> - <a>My Account</a> + <Link prefetch href="/add"> + <a>Sell</a> </Link> - <Signout /> - <UIContext> - {context => ( - <button onClick={context.toggle}> - My Cart - <CartCount - className="cart-count" - count={currentUser.me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)} - /> - </button> - )} - </UIContext> - </Fragment> + + {!me && ( + <Link prefetch href="/signup"> + <a>Sign In</a> + </Link> + )} + + {me && ( + <Fragment> + <Link href="/orders"> + <a>Orders</a> + </Link> + <Link href="/me"> + <a>My Account</a> + </Link> + <Signout /> + <UIContext> + {context => ( + <button onClick={context.toggle}> + My Cart + <CartCount + className="cart-count" + count={me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)} + /> + </button> + )} + </UIContext> + </Fragment> + )} + </StyledUl> )} - </StyledUl> + </Query> ); } } -export default compose(userEnhancer)(Nav); -export { Nav }; +export default Nav; diff --git a/frontend/components/Order.js b/frontend/components/Order.js index 409db66..a715483 100644 --- a/frontend/components/Order.js +++ b/frontend/components/Order.js @@ -1,60 +1,59 @@ import { Component } from 'react'; +import { Query } from 'react-apollo'; +import { format } from 'date-fns'; +import Head from 'next/head'; +import PropTypes from 'prop-types'; import { SINGLE_ORDER_QUERY } from '../queries'; -import { graphql, compose } from 'react-apollo'; -import { format, formatDistance } from 'date-fns'; import formatMoney from '../lib/formatMoney'; -import Item from './Item'; -import Head from 'next/head'; class Order extends Component { - componentDidMount() { - // this.props.orderQuery.refetch(); - } + static propTypes = { + id: PropTypes.string.isRequired, + }; + render() { - const { order } = this.props.orderQuery; - if (!order) { - return <p>No order Found!</p>; - } return ( - <div> - <Head> - <title>Sick Fits - Order {order.id}</title> - </Head> - <p> - <span>Order Id:</span> - <span>{order.id}</span> - </p> - <p> - <span>Charge</span> - <span>{order.charge}</span> - </p> - <p> - <span>Date</span> - <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span> - </p> - <p> - <span>Order Total</span> - <span>{formatMoney(order.total)}</span> - </p> - <p>You Bought {order.items.length} items in this order</p> - <div className="items"> - {order.items.map(item => ( - <div className="order-item" key={item.id}> - <h2> - {item.quantity} OF {item.title} - </h2> + <Query query={SINGLE_ORDER_QUERY} variables={{ id: this.props.id }}> + {({ data: { order }, error, loading }) => { + if (loading) return <p>Loading...</p>; + if (!order || error) return <p>No Order Found!</p>; + return ( + <div> + <Head> + <title>Sick Fits - Order {order.id}</title> + </Head> + <p> + <span>Order Id:</span> + <span>{order.id}</span> + </p> + <p> + <span>Charge</span> + <span>{order.charge}</span> + </p> + <p> + <span>Date</span> + <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span> + </p> + <p> + <span>Order Total</span> + <span>{formatMoney(order.total)}</span> + </p> + <p>You Bought {order.items.length} items in this order</p> + <div className="items"> + {order.items.map(item => ( + <div className="order-item" key={item.id}> + <h2> + {item.quantity} OF {item.title} + </h2> + </div> + ))} + </div> </div> - ))} - </div> - </div> + ); + }} + </Query> ); } } -const orderEnhancer = graphql(SINGLE_ORDER_QUERY, { - name: 'orderQuery', - options: ({ id }) => ({ - variables: { id }, - }), -}); -export default compose(orderEnhancer)(Order); +export default Order; diff --git a/frontend/components/OrderList.js b/frontend/components/OrderList.js index 6a1ad67..ed7beae 100644 --- a/frontend/components/OrderList.js +++ b/frontend/components/OrderList.js @@ -1,56 +1,48 @@ -import { Component } from 'react'; -import { USER_ORDERS_QUERY } from '../queries'; -import { graphql, compose } from 'react-apollo'; +import React from 'react'; +import { Query } from 'react-apollo'; import { format, formatDistance } from 'date-fns'; -import formatMoney from '../lib/formatMoney'; import Link from 'next/link'; +import { USER_ORDERS_QUERY } from '../queries'; -class OrderList extends Component { - componentDidMount() { - this.props.userOrdersQuery.refetch(); - } +class OrderList extends React.Component { render() { - if (this.props.loading) { - return <p>Loading...</p>; - } - - if (this.props.error) { - return <p>Error...</p>; - } - - const { orders } = this.props.userOrdersQuery; - - if (!orders || !orders.length) return <p>No Orders Yet!</p>; - return ( - <div> - <h2>You have {orders.length} Orders</h2> - <ul> - {orders.map(order => ( - <li key={order.id}> - <ul className="order-items"> - <hr /> - <h2> - <Link - href={{ - pathname: '/order', - query: { id: order.id }, - }} - > - <a>{order.id}</a> - </Link> - <time dateTime={order.createdAt} title={format(order.createdAt, 'MMMM D, YYYY h:mm A')}> - {formatDistance(order.createdAt, new Date())} - </time> - </h2> + <Query query={USER_ORDERS_QUERY}> + {({ data: { orders }, loading, error }) => { + if (loading) return <p>Loading...</p>; + if (error) return <p>Error...</p>; + if (!orders || !orders.length) return <p>No Orders Yet!</p>; + return ( + <div> + <h2>You have {orders.length} Orders</h2> + <ul> + {orders.map(order => ( + <li key={order.id}> + <ul className="order-items"> + <hr /> + <h2> + <Link + href={{ + pathname: '/order', + query: { id: order.id }, + }} + > + <a>{order.id}</a> + </Link> + <time dateTime={order.createdAt} title={format(order.createdAt, 'MMMM D, YYYY h:mm A')}> + {formatDistance(order.createdAt, new Date())} + </time> + </h2> + </ul> + </li> + ))} </ul> - </li> - ))} - </ul> - </div> + </div> + ); + }} + </Query> ); } } -const ordersEnhancer = graphql(USER_ORDERS_QUERY, { name: 'userOrdersQuery' }); -export default compose(ordersEnhancer)(OrderList); +export default OrderList; diff --git a/frontend/components/Page.js b/frontend/components/Page.js index 36854ef..bfe8492 100644 --- a/frontend/components/Page.js +++ b/frontend/components/Page.js @@ -53,9 +53,8 @@ const Page = ({ children }) => ( </ThemeProvider> </UIProvider> ); - Page.propTypes = { - children: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, }; export default Page; diff --git a/frontend/components/Pagination.js b/frontend/components/Pagination.js index abc22d0..138e1b2 100644 --- a/frontend/components/Pagination.js +++ b/frontend/components/Pagination.js @@ -1,10 +1,11 @@ import React from 'react'; -import { compose } from 'react-apollo'; +import { compose, Query } from 'react-apollo'; import styled from 'styled-components'; import Link from 'next/link'; import PropTypes from 'prop-types'; import { itemEnhancer } from '../enhancers/enhancers'; import { perPage } from '../config'; +import { ALL_ITEMS_QUERY } from '../queries/index'; const PaginationStyles = styled.div` text-align: center; @@ -30,56 +31,51 @@ const PaginationStyles = styled.div` } `; -const Pagination = props => { - if (props.loading) return <p>Loading...</p>; - const { aggregate } = props.itemsQuery.itemsConnection; - const { page } = props; - const pages = Math.ceil(aggregate.count / perPage); - - return ( - <PaginationStyles> - <Link - prefetch - href={{ - pathname: 'items', - query: { page: page - 1 }, - }} - > - <a className="prev" aria-disabled={page <= 1}> - โPrev - </a> - </Link> - <p> - Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong> - </p> - <p> - <strong>{aggregate.count}</strong> Items Total - </p> - <Link - prefetch - href={{ - pathname: 'items', - query: { page: page + 1 }, - }} - > - <a className="next" aria-disabled={page >= pages}> - Next โ - </a> - </Link> - </PaginationStyles> - ); -}; +const Pagination = props => ( + <Query query={ALL_ITEMS_QUERY}> + {({ data, loading, error }) => { + if (loading) return null; + const { aggregate } = data.itemsConnection; + const { page } = props; + const pages = Math.ceil(aggregate.count / perPage); + return ( + <PaginationStyles> + <Link + prefetch + href={{ + pathname: 'items', + query: { page: page - 1 }, + }} + > + <a className="prev" aria-disabled={page <= 1}> + โPrev + </a> + </Link> + <p> + Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong> + </p> + <p> + <strong>{aggregate.count}</strong> Items Total + </p> + <Link + prefetch + href={{ + pathname: 'items', + query: { page: page + 1 }, + }} + > + <a className="next" aria-disabled={page >= pages}> + Next โ + </a> + </Link> + </PaginationStyles> + ); + }} + </Query> +); Pagination.propTypes = { - itemsQuery: PropTypes.shape({ - itemsConnection: PropTypes.shape({ - aggregate: PropTypes.shape({ - count: PropTypes.number.isRequired, - }), - }), - }).isRequired, page: PropTypes.number.isRequired, }; -export default compose(itemEnhancer)(Pagination); -export { Pagination }; +export default Pagination; diff --git a/frontend/components/RemoveFromCart.js b/frontend/components/RemoveFromCart.js index 235ecce..d8c81b0 100644 --- a/frontend/components/RemoveFromCart.js +++ b/frontend/components/RemoveFromCart.js @@ -1,8 +1,8 @@ import { Component } from 'react'; -import { compose } from 'react-apollo'; +import { Mutation } from 'react-apollo'; import styled from 'styled-components'; import PropTypes from 'prop-types'; -import { removeFromCartEnhancer } from '../enhancers/enhancers'; +import { REMOVE_FROM_CART_MUTATION, CURRENT_USER_QUERY } from '../queries/index'; const BigButton = styled.button` font-size: 3rem; @@ -16,26 +16,27 @@ const BigButton = styled.button` class RemoveFromCart extends Component { static propTypes = { - removeFromCart: PropTypes.func.isRequired, id: PropTypes.string.isRequired, }; - handleRemoveFromCart = async () => { - await this.props.removeFromCart({ - variables: { - id: this.props.id, - }, - }); + update = (proxy, payload) => { + const data = proxy.readQuery({ query: CURRENT_USER_QUERY }); + const cartItemId = payload.data.removeFromCart.id; + data.me.cart = data.me.cart.filter(item => item.id !== cartItemId); + proxy.writeQuery({ query: CURRENT_USER_QUERY, data }); }; render() { return ( - <BigButton title="Remove From Cart" onClick={this.handleRemoveFromCart}> - × - </BigButton> + <Mutation mutation={REMOVE_FROM_CART_MUTATION} variables={{ id: this.props.id }} update={this.update}> + {removeFromCart => ( + <BigButton title="Remove From Cart" onClick={removeFromCart}> + × + </BigButton> + )} + </Mutation> ); } } -export default compose(removeFromCartEnhancer)(RemoveFromCart); -export { RemoveFromCart }; +export default RemoveFromCart; diff --git a/frontend/components/Reset.js b/frontend/components/Reset.js index 5593f75..5bc7f98 100644 --- a/frontend/components/Reset.js +++ b/frontend/components/Reset.js @@ -1,13 +1,18 @@ -import React, { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; -import { RESET_MUTATION, CURRENT_USER_QUERY } from '../queries'; +import React from 'react'; +import { Mutation } from 'react-apollo'; +import PropTypes from 'prop-types'; import Form from './styles/Form'; +import Error from './ErrorMessage'; +import { RESET_MUTATION, CURRENT_USER_QUERY } from '../queries'; + +class Reset extends React.Component { + static propTypes = { + resetToken: PropTypes.string.isRequired, + }; -class Reset extends Component { state = { confirmPassword: '', password: '', - errors: [], }; saveToState = e => { @@ -15,74 +20,58 @@ class Reset extends Component { this.setState({ [name]: value }); }; - resetPassword = async e => { - console.log(e); + resetPassword = async (e, resetMutation) => { e.preventDefault(); - - if (this.state.password !== this.state.confirmPassword) { - this.setState({ errors: [{ message: 'Passwords Must Match!' }] }); - } - - const res = await this.props.resetPassword({ - variables: { - resetToken: this.props.resetToken, - password: this.state.password, - confirmPassword: this.state.confirmPassword, - }, - }); - - if (res.errors) { - this.setState({ errors: res.errors }); - return; - } - console.log('Back!'); + const res = await resetMutation(); console.log(res); // sign them in! localStorage.setItem('token', res.data.resetPassword.token); - // refresh the current user query - this.props.currentUser.refetch(); + // TODO: Manually call refresh on a Query }; render() { return ( - <div> - {this.state.loading ? 'LOADING...' : 'Ready!'} - - {this.state.errors ? this.state.errors.map((err, i) => <p key={i}>{err.message}</p>) : null} - - <Form onSubmit={this.resetPassword}> - <label htmlFor="password"> - password - <input - value={this.state.password} - onChange={this.saveToState} - name="password" - type="password" - id="password" - /> - </label> - <label htmlFor="confirm"> - Confirm: - <input - value={this.state.confirmPassword} - onChange={this.saveToState} - name="confirmPassword" - type="password" - id="confirmPassword" - /> - </label> + <Mutation + mutation={RESET_MUTATION} + variables={{ + resetToken: this.props.resetToken, + password: this.state.password, + confirmPassword: this.state.confirmPassword, + }} + refetchQueries={[{ query: CURRENT_USER_QUERY }]} + > + {(resetMutation, { error, loading }) => ( + <Form onSubmit={e => this.resetPassword(e, resetMutation)}> + <Error error={error} /> + <fieldset disabled={loading} aria-busy={loading}> + <label htmlFor="password"> + password + <input + value={this.state.password} + onChange={this.saveToState} + name="password" + type="password" + id="password" + /> + </label> + <label htmlFor="confirm"> + Confirm: + <input + value={this.state.confirmPassword} + onChange={this.saveToState} + name="confirmPassword" + type="password" + id="confirmPassword" + /> + </label> - <button type="submit">Request Reset!</button> - </Form> - </div> + <button type="submit">Request Reset!</button> + </fieldset> + </Form> + )} + </Mutation> ); } } -// When we submit this mutation, we need to update our store - we have a few ways to do that: -// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency. - -const restEnahncer = graphql(RESET_MUTATION, { name: 'resetPassword' }); -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }); - -export default compose(restEnahncer, userEnhancer)(Reset); +export default Reset; diff --git a/frontend/components/ResetRequest.js b/frontend/components/ResetRequest.js index 0910e5f..fb6f660 100644 --- a/frontend/components/ResetRequest.js +++ b/frontend/components/ResetRequest.js @@ -1,52 +1,45 @@ -import React, { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; -import PropTypes from 'prop-types'; +import React from 'react'; +import { Mutation } from 'react-apollo'; import { REQUEST_RESET_MUTATION } from '../queries'; import Form from './styles/Form'; +import Error from './ErrorMessage'; -class ResetRequest extends Component { - static propTypes = { - requestReset: PropTypes.func.isRequired, - }; - +class ResetRequest extends React.Component { state = { email: '', - errors: [], - }; - - requestReset = async e => { - e.preventDefault(); - const res = await this.props.requestReset({ - variables: { - email: this.state.email, - }, - }); - if (res.errors) { - this.setState({ errors: res.errors }); - } - }; - - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); }; render() { return ( - <Form onSubmit={this.requestReset}> - {this.state.errors ? this.state.errors.map((err, i) => <p key={i}>{err.message}</p>) : null} - <label htmlFor="email"> - Email - <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" /> - </label> + <Mutation mutation={REQUEST_RESET_MUTATION} variables={this.state}> + {(resetMutation, { loading, error }) => ( + <Form + onSubmit={async e => { + e.preventDefault(); + const res = await resetMutation(); + console.log(res); + }} + > + <Error error={error} /> + <fieldset disabled={loading} aria-busy={loading}> + <label htmlFor="email"> + Email + <input + value={this.state.email} + onChange={e => this.setState({ email: e.target.value })} + name="email" + type="text" + placeholder="email" + /> + </label> - <button type="submit">Request Reset!</button> - </Form> + <button type="submit">Request Reset!</button> + </fieldset> + </Form> + )} + </Mutation> ); } } -const requestResetEnhancer = graphql(REQUEST_RESET_MUTATION, { name: 'requestReset' }); - -export default compose(requestResetEnhancer)(ResetRequest); -export { ResetRequest }; +export default ResetRequest; diff --git a/frontend/components/Search.js b/frontend/components/Search.js index d30789c..45f5961 100644 --- a/frontend/components/Search.js +++ b/frontend/components/Search.js @@ -1,15 +1,16 @@ import Downshift from 'downshift'; -import { graphql, compose } from 'react-apollo'; +import { Query } from 'react-apollo'; +import Router from 'next/router'; import styled from 'styled-components'; import { SEARCH_ITEMS_QUERY } from '../queries'; -import { Router } from '../routes'; function routeToItem(item) { - console.log('TODO: UPdate routeToItem function'); - // Router.pushRoute('item', { - // slug: slugify(item.title), - // itemId: item.id, - // }); + Router.push({ + pathname: '/item', + query: { + id: item.id, + }, + }); } const DropDown = styled.div` @@ -52,7 +53,6 @@ function AutoComplete(props) { placeholder: 'Search For Item', id: 'search', onChange: e => { - console.log('Change happened'); props.refetch({ searchTerm: e.target.value }); }, style: { fontSize: '20px', padding: '10px', display: 'block', width: '100%' }, @@ -75,21 +75,15 @@ function AutoComplete(props) { ); } -const Search = props => ( +// TODO: This should not fire a queryon page load +const Search = () => ( <SearchStyles> - <AutoComplete - items={props.searchItems.items} - onChange={selectedItem => console.log(selectedItem)} - refetch={props.searchItems.refetch} - /> + <Query query={SEARCH_ITEMS_QUERY} variables={{ searchTerm: '' }}> + {({ data, error, refetch }) => ( + <AutoComplete items={data.items || []} onChange={selectedItem => console.log(selectedItem)} refetch={refetch} /> + )} + </Query> </SearchStyles> ); -const searchEnhancer = graphql(SEARCH_ITEMS_QUERY, { - name: 'searchItems', - options: { - // TODO: Why is this camo - variables: { searchTerm: 'camo' }, - }, -}); -export default compose(searchEnhancer)(Search); +export default Search; diff --git a/frontend/components/Signin.js b/frontend/components/Signin.js index 423e9e8..a211528 100644 --- a/frontend/components/Signin.js +++ b/frontend/components/Signin.js @@ -1,34 +1,21 @@ import React, { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; +import { Mutation } from 'react-apollo'; import { SIGNIN_MUTATION, CURRENT_USER_QUERY } from '../queries'; - +import Error from './ErrorMessage'; import Form from './styles/Form'; class Signin extends Component { state = { email: `wesbos@gmail.com`, password: 'abc123', - errors: [], }; - loginUser = async e => { + loginUser = async (e, signin) => { e.preventDefault(); - // pull the values from state - const { email, password } = this.state; - this.setState({ loading: true, errors: [] }); - const res = await this.props.signin({ - // pass in those variables from state - variables: { name, email, password }, - }); - if (res.errors) { - this.setState({ errors: res.errors }); - return; - } + const res = await signin(); localStorage.setItem('token', res.data.signin.token); + return Promise.resolve('it worked'); // TODO refetch current user query - this.props.currentUser.refetch(); - - this.setState({ loading: false }); }; saveToState = e => { @@ -38,41 +25,42 @@ class Signin extends Component { render() { return ( - <div> - {this.state.loading ? 'LOADING...' : null} - - {this.state.errors ? this.state.errors.map(err => <p>{err.message}</p>) : null} + <Mutation mutation={SIGNIN_MUTATION} variables={this.state} refetchQueries={[{ query: CURRENT_USER_QUERY }]}> + {(signin, { data, loading, error }) => ( + <Form onSubmit={e => this.loginUser(e, signin)}> + <Error error={error} /> + <fieldset disabled={loading} aria-busy={loading}> + <label htmlFor="email"> + Email + <input + value={this.state.email} + onChange={this.saveToState} + name="email" + type="text" + placeholder="email" + /> + </label> - <Form onSubmit={this.loginUser}> - <label htmlFor="email"> - Email - <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" /> - </label> + <label htmlFor="password"> + Password + <input + type="password" + name="password" + id="password" + className="password" + placeholder="password" + value={this.state.password} + onChange={this.saveToState} + /> + </label> - <label htmlFor="password"> - Password - <input - type="password" - name="password" - id="password" - className="password" - placeholder="password" - value={this.state.password} - onChange={this.saveToState} - /> - </label> - - <button type="submit">Sign In!</button> - </Form> - </div> + <button type="submit">Sign In!</button> + </fieldset> + </Form> + )} + </Mutation> ); } } -// When we submit this mutation, we need to update our store - we have a few ways to do that: -// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency. - -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }); -const signinEnhancer = graphql(SIGNIN_MUTATION, { name: 'signin' }); - -export default compose(signinEnhancer, userEnhancer)(Signin); +export default Signin; diff --git a/frontend/components/Signout.js b/frontend/components/Signout.js index 8581009..d7c7816 100644 --- a/frontend/components/Signout.js +++ b/frontend/components/Signout.js @@ -1,20 +1,21 @@ import React, { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; +import { Query } from 'react-apollo'; import { CURRENT_USER_QUERY } from '../queries'; class Signout extends Component { - signout = () => { + signout = refetch => { console.log('Signing Out'); localStorage.removeItem('token'); - this.props.currentUser.refetch(); + refetch(); }; render() { - const { me, loading, error } = this.props.currentUser; - if (!me || loading || error) return null; - return <button onClick={this.signout}>Sign Out</button>; + return ( + <Query query={CURRENT_USER_QUERY}> + {({ refetch }) => <button onClick={() => this.signout(refetch)}>Sign Out</button>} + </Query> + ); } } -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }); -export default compose(userEnhancer)(Signout); +export default Signout; diff --git a/frontend/components/Signup.js b/frontend/components/Signup.js index 3a4dd99..314d0ab 100644 --- a/frontend/components/Signup.js +++ b/frontend/components/Signup.js @@ -1,38 +1,15 @@ import React, { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; +import { graphql, compose, Mutation } from 'react-apollo'; import { SIGNUP_MUTATION, CURRENT_USER_QUERY } from '../queries'; import Form from './styles/Form'; +import catchError from '../lib/catchError'; +import Error from './ErrorMessage'; class Signup extends Component { state = { - email: `wesbos+${Date.now()}@gmail.com`, + email: `wesbos@gmail.com`, name: 'Wes Bos', - password: 'abc123', - error: undefined, - }; - - createUser = async e => { - e.preventDefault(); - // pull the values from state - const { email, name, password } = this.state; - // create a mutation - // TODO: handle any errors - // turn loading on - this.setState({ loading: true }); - console.log({ name, email, password }); - try { - const res = await this.props.signup({ - // pass in those variables from state - variables: { name, email, password }, - }); - localStorage.setItem('token', res.data.signup.token); - // TODO refetch current user query - this.props.currentUser.refetch(); - } catch (error) { - this.setState({ error }); - console.dir(error); - } - this.setState({ loading: false }); + password: 'wes', }; saveToState = e => { @@ -42,52 +19,53 @@ class Signup extends Component { render() { return ( - <div> - {this.state.loading ? 'LOADING...' : null} - - {this.state.error ? <p>{this.state.error.message}</p> : null} - - <Form onSubmit={this.createUser}> - <label htmlFor="email"> - Email - <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" /> - </label> + <Mutation mutation={SIGNUP_MUTATION} variables={this.state}> + {(signup, { data, loading, error }) => ( + <Form + onSubmit={e => { + e.preventDefault(); + signup(); + }} + > + <fieldset disabled={loading} aria-busy={loading}> + <Error error={error} /> + <h2>Sign Up for an Account</h2> + <label htmlFor="email"> + Email + <input + value={this.state.email} + onChange={this.saveToState} + name="email" + type="text" + placeholder="email" + /> + </label> - <label htmlFor="name"> - Name - <input type="text" name="name" placeholder="name" value={this.state.name} onChange={this.saveToState} /> - </label> + <label htmlFor="name"> + Name + <input type="text" name="name" placeholder="name" value={this.state.name} onChange={this.saveToState} /> + </label> - <label htmlFor="password"> - Password - <input - type="password" - name="password" - id="password" - className="password" - placeholder="password" - value={this.state.password} - onChange={this.saveToState} - /> - </label> + <label htmlFor="signupPassword"> + Password + <input + type="password" + name="password" + id="signupPassword" + className="password" + placeholder="password" + value={this.state.password} + onChange={this.saveToState} + /> + </label> - <button type="submit">Submit</button> - </Form> - </div> + <button type="submit">Submit</button> + </fieldset> + </Form> + )} + </Mutation> ); } } -// When we submit this mutation, we need to update our store - we have a few ways to do that: -// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency. - -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }); - -const signupEnhancer = graphql(SIGNUP_MUTATION, { - name: 'signup', - options: { - refetchQueries: ['currentUser'], - }, -}); - -export default compose(userEnhancer, signupEnhancer)(Signup); +export default Signup; diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js index 437fff1..5a6373f 100644 --- a/frontend/components/SingleItem.js +++ b/frontend/components/SingleItem.js @@ -6,6 +6,10 @@ const SingleItem = ({ findItem: { error, loading, items } }) => { if (error) return <p>Error...</p>; const item = items[0]; + if (!item) { + return <p>No Item Found</p>; + } + return ( <div> <img src={item.largeImage || item.image} alt={item.title} /> diff --git a/frontend/components/TakeMyMoney.js b/frontend/components/TakeMyMoney.js index 011451a..20fc334 100644 --- a/frontend/components/TakeMyMoney.js +++ b/frontend/components/TakeMyMoney.js @@ -1,16 +1,23 @@ import { Component } from 'react'; import StripeCheckout from 'react-stripe-checkout'; -import { compose } from 'react-apollo'; +import { compose, Mutation, Query } from 'react-apollo'; import Router from 'next/router'; import NProgress from 'nprogress'; import PropTypes from 'prop-types'; -// import { CREATE_ORDER_MUTATION, CURRENT_USER_QUERY } from '../queries'; -import { userEnhancer, createOrderEnhancer } from '../enhancers/enhancers'; +import { CREATE_ORDER_MUTATION, CURRENT_USER_QUERY } from '../queries'; + +function totalItems(cart) { + return cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); +} + +function total(cart) { + return cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0); +} class TakeMyMoney extends Component { - onToken = async res => { + onToken = async (res, createOrder) => { NProgress.start(); - const order = await this.props.createOrder({ + const order = await createOrder({ variables: { token: res.id, }, @@ -18,29 +25,33 @@ class TakeMyMoney extends Component { // Route them to that order page const { id } = order.data.createOrder; Router.push({ - pathname: `/order/${id}`, + pathname: `/order`, query: { id }, }); }; - + // TODO - refetchQueries after an order is created render() { - const { me } = this.props.currentUser; - if (!me || !me.cart.length) return null; - const total = me.cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0); - const totalItems = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); return ( - <StripeCheckout - amount={total} - name="Sick Fits Haul" - description={`Order of ${totalItems} Items From Sick Fits`} - image={me.cart[0].item.image} - token={this.onToken} - stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps" - currency="USD" - email={me.email} - > - {this.props.children} - </StripeCheckout> + <Query query={CURRENT_USER_QUERY}> + {({ data: { me } }) => ( + <Mutation mutation={CREATE_ORDER_MUTATION}> + {createOrder => ( + <StripeCheckout + amount={total(me.cart)} + name="Sick Fits Haul" + description={`Order of ${totalItems(me.cart)} Items From Sick Fits`} + image={me.cart[0].item.image} + token={res => this.onToken(res, createOrder)} + stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps" + currency="USD" + email={me.email} + > + {this.props.children} + </StripeCheckout> + )} + </Mutation> + )} + </Query> ); } } @@ -50,5 +61,7 @@ TakeMyMoney.propTypes = { createOrder: PropTypes.func.isRequired, }; -export default compose(userEnhancer, createOrderEnhancer)(TakeMyMoney); -export { TakeMyMoney }; +// export default compose(userEnhancer, createOrderEnhancer)(TakeMyMoney); +// export { TakeMyMoney }; + +export default TakeMyMoney; diff --git a/frontend/components/UpdateItem.js b/frontend/components/UpdateItem.js index e97b4aa..4841006 100644 --- a/frontend/components/UpdateItem.js +++ b/frontend/components/UpdateItem.js @@ -9,9 +9,7 @@ class UpdateItem extends Component { state = { item: {}, }; - componentDidMount() { - console.log(this.props); - } + saveToState = e => { let { name, value, type } = e.target; diff --git a/frontend/components/styles/Form.js b/frontend/components/styles/Form.js index 09085ad..4fd8029 100644 --- a/frontend/components/styles/Form.js +++ b/frontend/components/styles/Form.js @@ -1,11 +1,22 @@ -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; + +const loading = keyframes` + from { + background-position: 0 0; + /* rotate: 0; */ + } + + to { + background-position: 100% 100%; + /* rotate: 360deg; */ + } +`; const Form = styled.form` box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.05); background: rgba(0, 0, 0, 0.02); border: 5px solid white; - margin: 100px; - padding: 100px; + padding: 20px; font-size: 2.5rem; font-weight: 600; label { @@ -36,6 +47,24 @@ const Form = styled.form` font-weight: 600; padding: 1rem 2rem; } + fieldset { + border: 0; + padding: 0; + + &[disabled] { + opacity: 0.5; + } + &::before { + height: 10px; + content: ''; + display: block; + background-image: linear-gradient(to right, #ff3019 0%, #e2b04a 50%, #ff3019 100%); + } + &[aria-busy='true']::before { + background-size: 50% auto; + animation: ${loading} 0.5s linear infinite; + } + } `; Form.displayName = 'Form'; |
