diff options
| author | Wes Bos <wesbos@gmail.com> | 2017-09-21 15:39:12 -0400 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2017-09-21 15:39:12 -0400 |
| commit | 3b45dd7b593825721ec9ee9f6a49c732601b1ae0 (patch) | |
| tree | a2e8e73d6b6b6bc32eda2bb79af817397e4304f5 /components | |
| parent | 1450e579c67e3d969cb099dfe6c1cefd1e313fb5 (diff) | |
Cha Ching
Diffstat (limited to 'components')
| -rw-r--r-- | components/AddToCart.js | 84 | ||||
| -rw-r--r-- | components/Cart.js | 35 | ||||
| -rw-r--r-- | components/CartList.js | 54 | ||||
| -rw-r--r-- | components/ChaChing.js | 56 | ||||
| -rw-r--r-- | components/CreateItem.js | 113 | ||||
| -rw-r--r-- | components/ErrorMessage.js | 32 | ||||
| -rw-r--r-- | components/Item.js | 84 | ||||
| -rw-r--r-- | components/Items.js | 119 | ||||
| -rw-r--r-- | components/LoginAuth0.js | 58 | ||||
| -rw-r--r-- | components/Meta.js | 7 | ||||
| -rw-r--r-- | components/Nav.js | 54 | ||||
| -rw-r--r-- | components/Page.js | 10 | ||||
| -rw-r--r-- | components/Pagination.js | 70 | ||||
| -rw-r--r-- | components/Search.js | 103 | ||||
| -rw-r--r-- | components/SingleItem.js | 36 |
15 files changed, 556 insertions, 359 deletions
diff --git a/components/AddToCart.js b/components/AddToCart.js index 0c40efe..9bcdb7f 100644 --- a/components/AddToCart.js +++ b/components/AddToCart.js @@ -1,6 +1,37 @@ import { Component } from 'react'; -import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY } from '../queries'; +import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries'; +import { removeFromCartEnhancer, userEnhancer } from '../enhancers'; import { graphql, compose } from 'react-apollo'; +import Transition from 'react-transition-group/Transition'; +import makeImage from '../lib/image'; +import styled from 'styled-components'; + +const JumpImg = styled.img` + border: 0 solid black; + max-width: 100px; + transition: all 0.5s; + position: fixed; + left: ${props => props.x}px; + top: -100%; + &.jump-entered { + border-color: green; + transform-origin: 0 0; + transform: scale(0); + top: ${props => props.y}px; + left: ${props => props.x}px; + } + &.jump-entering { + border-color: yellow; + top: ${props => props.y}px; + left: ${props => props.x}px; + } + &.jump-exited { + border-color: red; + } + &.jump-exiting { + border-color: yellow; + } +`; class AddToCart extends Component { componentDidMount() { @@ -12,28 +43,55 @@ class AddToCart extends Component { variables: { userId: this.props.currentUserQuery.user.id, itemId: this.props.id, - } + }, }); this.props.currentUserQuery.refetch(); - console.log(res) - } + console.log(res); + }; + + removeFromCart = async () => { + const res = await this.props.removeFromCart({ + variables: { + userId: this.props.currentUserQuery.user.id, + itemId: this.props.id, + }, + }); + this.props.currentUserQuery.refetch(); + console.log(res); + }; + render() { const user = this.props.currentUserQuery.user; + if (!user) return <p>Loading...</p>; const cartIds = user.cart.map(item => item.id); + const image = this.props.singleItemQuery.Item.image; + const isInCart = cartIds.includes(this.props.id); + const { x, y } = document.querySelector('.cart').getBoundingClientRect(); + console.log({ x, y }); + return ( <div> - - { - cartIds.includes(this.props.id) - ? <button>Added to cart β
</button> - : <button onClick={this.addToCart}>Add To Cart π</button> - } + {isInCart ? ( + <button onClick={this.removeFromCart}>β Remove From Cart</button> + ) : ( + <button onClick={this.addToCart}>Add To Cart π</button> + )} + <Transition in={isInCart} timeout={1000}> + {status => <JumpImg x={x} y={y} src={makeImage(image)} className={`jump-${status}`} />} + </Transition> </div> - ) + ); } } -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); const createOrderEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart' }); -export default compose(userEnhancer, createOrderEnhancer)(AddToCart); +const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, { + name: 'singleItemQuery', + options: ({ id }) => ({ + variables: { + id, + }, + }), +}); +export default compose(userEnhancer, createOrderEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart); diff --git a/components/Cart.js b/components/Cart.js index 57a00fc..88b072c 100644 --- a/components/Cart.js +++ b/components/Cart.js @@ -1,12 +1,17 @@ -import { Component } from "react"; -import { graphql, compose } from "react-apollo"; -import { CURRENT_USER_QUERY } from "../queries"; -import styled from "styled-components"; -import formatMoney from "../lib/formatMoney.js"; +import { Component } from 'react'; +import { graphql, compose } from 'react-apollo'; +import styled from 'styled-components'; +import { CURRENT_USER_QUERY } from '../queries'; +import formatMoney from '../lib/formatMoney.js'; +import ChaChing from './ChaChing'; -const cartStyles = styled.div` +const CartStyles = styled.div` background: white; + border-radius: 10px; padding: 20px; + overflow: hidden; + display: flex; + align-items: center; `; class Cart extends Component { @@ -14,7 +19,6 @@ class Cart extends Component { // This fetches the new data, but doesn't populate the user via props // this.props.currentUserQuery.refetch(); // This fetches the new data, and populates the user via props - console.log("refetching!"); setTimeout(this.props.currentUserQuery.refetch, 1); } @@ -23,19 +27,20 @@ class Cart extends Component { const { loading, error } = this.props.currentUserQuery; const { user } = this.props.currentUserQuery; if (loading || error || !user) return <p>Cart Loading...</p>; - const { email = "" } = user; const total = user.cart.reduce((a, b) => a + b.price, 0); return ( - <div> - <p> - π° There are <strong>{user.cart.length}</strong> Items in your cart - totaling <strong>{formatMoney(total)}</strong> - </p> - </div> + <CartStyles className="cart"> + There + {user.cart.length === 1 ? ' is ' : 'are '} + <ChaChing amount={user.cart.length} /> + {user.cart.length === 1 ? ' item ' : ' items '} + in your cart totaling + <ChaChing amount={formatMoney(total)} /> + </CartStyles> ); } } -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: "currentUserQuery" }); +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); export default compose(userEnhancer)(Cart); diff --git a/components/CartList.js b/components/CartList.js index d0456aa..d485907 100644 --- a/components/CartList.js +++ b/components/CartList.js @@ -5,31 +5,30 @@ import Signup from '../components/Signup'; import LoginAuth0 from '../components/LoginAuth0'; import Page from '../components/Page'; import { USER_ORDERS_QUERY } from '../queries'; -import { graphql, compose } from 'react-apollo' +import { graphql, compose } from 'react-apollo'; import has from 'lodash.has'; import get from 'lodash.get'; import formatMoney from '../lib/formatMoney'; import makeImage from '../lib/image'; -import TakeMyMoney from './TakeMyMoney' - -import { CURRENT_USER_QUERY, REMOVE_FROM_CART_MUTATION } from '../queries'; +import TakeMyMoney from './TakeMyMoney'; +import { removeFromCartEnhancer } from '../enhancers'; +import { CURRENT_USER_QUERY } from '../queries'; class CartList extends Component { componentDidMount() { setTimeout(this.props.currentUserQuery.refetch, 1); } render() { - - if(this.props.loading) { - return <p>Loading...</p> + if (this.props.loading) { + return <p>Loading...</p>; } - if(this.props.error) { - return <p>Error...</p> + if (this.props.error) { + return <p>Error...</p>; } - if(!has(this.props, 'currentUserQuery.user.cart')) { - return <p>Don't have it yet!</p> + if (!has(this.props, 'currentUserQuery.user.cart')) { + return <p>Don't have it yet!</p>; } const cart = this.props.currentUserQuery.user.cart; @@ -43,32 +42,27 @@ class CartList extends Component { {cart.map(item => ( <li key={item.id}> {item.title} - <button onClick={() => this.props.removeFromCart({ variables: { - userId, - itemId: item.id - }})}>× Delete</button> + <button + onClick={() => + this.props.removeFromCart({ + variables: { + userId, + itemId: item.id, + }, + })} + > + × Delete + </button> </li> ))} </ul> - <TakeMyMoney - amount={total} - name="Testing 123" - description="Test test 123" - > + <TakeMyMoney amount={total} name="Testing 123" description="Test test 123"> <button>Buy for {formatMoney(total)}</button> </TakeMyMoney> </div> - ) + ); } } const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -const removeItemEnhancer = graphql(REMOVE_FROM_CART_MUTATION, { name: 'removeFromCart', options: { - update: (proxy, payload) => { - const data = proxy.readQuery({ query: CURRENT_USER_QUERY }); - const cartItemId = payload.data.removeFromCartItems.cartItem.id; - data.user.cart = data.user.cart.filter(item => item.id !== cartItemId); - proxy.writeQuery({ query: CURRENT_USER_QUERY, data }); - }, - } }); -export default compose(userEnhancer, removeItemEnhancer)(CartList) +export default compose(userEnhancer, removeFromCartEnhancer)(CartList); diff --git a/components/ChaChing.js b/components/ChaChing.js new file mode 100644 index 0000000..5874c27 --- /dev/null +++ b/components/ChaChing.js @@ -0,0 +1,56 @@ +import TransitionGroup from 'react-transition-group/TransitionGroup'; +import Transition from 'react-transition-group/Transition'; +import styled from 'styled-components'; + +const CartCount = styled.div` + padding: 10px; + transition: all 0.5s; + display: inline-block; + background: white; + background: linear-gradient(to bottom, #eeeeee 0%, #eeeeee 100%); + transform: translateY(100%); + z-index: 1; + position: relative; + &.cart-entering { + background: green; + transform: translateY(100%); + } + &.cart-entered { + transform: translateY(0); + } + &.cart-exiting { + position: absolute; + z-index: 0; + left: 0; + transform: translateY(-100%); + } +`; + +const ChaChingStyles = styled.div` + position: relative; + display: inline-block; + overflow: hidden; +`; + +const ChaChing = ({ amount }) => ( + <ChaChingStyles> + <TransitionGroup> + <Transition + in + timeout={{ + enter: 0, + exit: 500, + }} + key={amount} + > + {status => ( + <CartCount className={`cart-${status}`} key={`count-${amount}`}> + {amount} + </CartCount> + )} + </Transition> + </TransitionGroup> + </ChaChingStyles> +); + +export default ChaChing; diff --git a/components/CreateItem.js b/components/CreateItem.js index 3b86f2c..d9dc97b 100644 --- a/components/CreateItem.js +++ b/components/CreateItem.js @@ -1,99 +1,124 @@ -import React, { Component } from 'react' -import { graphql, gql } from 'react-apollo' +import React, { Component } from 'react'; +import { graphql, gql } from 'react-apollo'; import { ALL_ITEMS_QUERY, CREATE_LINK_MUTATION } from '../queries'; +import ErrorMessage from './ErrorMessage'; class CreateLink extends Component { - state = { description: '', title: '', image: '', price: 0, fullPrice: 0, - loading: false - } + loading: false, + error: { + message: 'shit!', + }, + }; componentWillReceiveProps(nextProps) { console.log(nextProps); } - uploadFile = async (e) => { + uploadFile = async e => { const files = e.currentTarget.files; - let data = new FormData(); + const data = new FormData(); data.append('data', files[0]); // use the file endpoint const res = await fetch('https://api.graph.cool/file/v1/cj5xz8szs28930145gct82bdj', { method: 'POST', - body: data + body: data, }); const file = await res.json(); console.log(file); this.setState({ image: file.id }); - } + }; + + _createLink = async e => { + e.preventDefault(); + // pull the values from state + const { description, title, image, price, fullPrice } = this.state; + // create a mutation + // TODO: handle any errors + // turn loading on + this.setState({ loading: true }); + try { + const res = await this.props.createLinkMutation({ + // pass in those variables from state + variables: { + description, + title, + price: parseInt(price), + fullPrice, + imageId: image, + }, + }); + } catch (error) { + this.setState({ error }); + console.log(error); + } + this.setState({ loading: false }); + }; render() { return ( <div> - { this.state.loading ? 'LOADING...' : 'Ready!' } + {this.state.loading ? 'LOADING...' : 'Ready!'} + + <ErrorMessage error={this.state.error} onButtonClick={() => this.setState({ error: {} })} /> <form onSubmit={this._createLink}> <p> Image - <input onChange={this.uploadFile} type='file'/> + <input onChange={this.uploadFile} type="file" /> </p> - <p>Title - <input value={this.state.title} onChange={(e) => this.setState({ title: e.target.value })} type='text' placeholder='A description for the link'/> + <p> + Title + <input + value={this.state.title} + onChange={e => this.setState({ title: e.target.value })} + type="text" + placeholder="A description for the link" + /> </p> - <label>Price<input type="number" min="0" value={this.state.price} onChange={(e) => this.setState({ price: e.target.value })} /></label> + <label> + Price<input + type="number" + min="0" + value={this.state.price} + onChange={e => this.setState({ price: e.target.value })} + /> + </label> <textarea value={this.state.description} - onChange={(e) => this.setState({ description: e.target.value })} - type='text' - placeholder='The desc for this item' - ></textarea> + onChange={e => this.setState({ description: e.target.value })} + type="text" + placeholder="The desc for this item" + /> <button type="submit">Submit</button> </form> </div> - ) - } - - _createLink = async (e) => { - e.preventDefault(); - // pull the values from state - const { description, title, image, price, fullPrice } = this.state; - // create a mutation - // TODO: handle any errors - // turn loading on - this.setState({ loading: true }); - const res = await this.props.createLinkMutation({ - // pass in those variables from state - variables: { - description, - title, - price: parseInt(price), - fullPrice, - imageId: image - } - }); - this.setState({ loading: false }); + ); } } // 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. - -export default graphql(CREATE_LINK_MUTATION, { name: 'createLinkMutation', options: { +export default graphql(CREATE_LINK_MUTATION, { + name: 'createLinkMutation', + options: { // Easy, but slow // refetchQueries: ['AllLinksQuery'] // This is much Better / efficient // Notice how the variable is called createItem - that is because createItem is the name of the query! update: (proxy, { data: { createItem } }) => { - console.log({createItem, ALL_ITEMS_QUERY}); + console.log({ createItem, ALL_ITEMS_QUERY }); const data = proxy.readQuery({ query: ALL_ITEMS_QUERY }); // data is our store, allItems is our sub-"state", it's just an array. We can just add it to data.allItems.unshift(createItem); // and then "set state", so it will update on the page. This will update the cache for us! proxy.writeQuery({ query: ALL_ITEMS_QUERY, data }); }, - } })(CreateLink) + }, +})(CreateLink); diff --git a/components/ErrorMessage.js b/components/ErrorMessage.js new file mode 100644 index 0000000..12de51d --- /dev/null +++ b/components/ErrorMessage.js @@ -0,0 +1,32 @@ +import styled from 'styled-components'; +import React from 'react'; + +import PropTypes from 'prop-types'; + +const StyledError = styled.div` + background: pink; + border: 2px solid red; + padding: 20px; + display: flex; + p { + margin: 0; + flex: 1 0 auto; + } +`; + +const DisplayError = props => { + if (!props.error || !props.error.message) return null; + return ( + <StyledError> + <p>{props.error.message}</p> + <button onClick={props.onButtonClick}>×</button> + </StyledError> + ); +}; + +DisplayError.propTypes = { + error: PropTypes.object.isRequired, + onButtonClick: PropTypes.func.isRequired, +}; + +export default DisplayError; diff --git a/components/Item.js b/components/Item.js index 66ca66f..1b47fcf 100644 --- a/components/Item.js +++ b/components/Item.js @@ -1,33 +1,61 @@ -import React, { Component } from 'react' -import { graphql, gql, compose } from 'react-apollo' -import { SINGLE_ITEM_QUERY } from '../queries'; -import withData from '../lib/withData'; +import styled from 'styled-components'; +import slugify from 'slugify'; +import { Link } from '../routes'; +import AddToCart from './AddToCart'; +import makeImage from '../lib/image'; +import TakeMyMoney from './TakeMyMoney'; +import formatMoney from '../lib/formatMoney'; -class Item extends Component { - render() { - if(!this.props.findItem.Item) return <p>Not ready</p> - console.log(this.props.findItem.Item); +const Item = styled.div` + background: #f3f3f3; + padding: 5px; + img { + width: 100%; + } +`; - if(this.props.loading) return <p>Loading...</p> - if(this.props.error) return <p>Error...</p> +const ItemComponent = ({ item }) => ( + <Item key={item.id}> + {item.image ? <img key={item.image.secret} src={makeImage(item.image)} alt={item.title} /> : null} + <h3> + <Link + route="item" + params={{ + slug: slugify(item.title), + itemId: item.id, + }} + > + <a>{item.title}</a> + </Link> + </h3> - const item = this.props.findItem.Item; - return ( - <div> - <h2>Viewing {item.title}</h2> - </div> - ) - } -} + <p>{item.description}</p> + {/* { + + <Link + href={{ + pathname: '/admin/update', + query: { id: item.id }, + }} + > + <a>Edit βοΈ</a> + </Link> + + } */} + + <TakeMyMoney + id={item.id} + amount={item.price} + name={item.title} // the pop-in header title + description={item.description} // the pop-in header subtitle + image={makeImage(item.image)} + > + <button>Buy for {formatMoney(item.price)}</button> + </TakeMyMoney> -const ComponentWithMutations = compose( - graphql(SINGLE_ITEM_QUERY, { - name: 'findItem', - // This comes from Props - options: ({ id }) => ({ - variables: { id } - }) - }) -)(Item); + <AddToCart id={item.id} /> + <button onClick={() => this.props.removeItemMutation({ variables: { id: item.id } })}>× Delete item</button> + </Item> +); -export default ComponentWithMutations; +export default ItemComponent; diff --git a/components/Items.js b/components/Items.js index d212ead..053c9af 100644 --- a/components/Items.js +++ b/components/Items.js @@ -1,20 +1,12 @@ -import { Component } from 'react' -import { withApollo, graphql, compose } from 'react-apollo' -import UpdateItem from './UpdateItem'; -import Link from 'next/link'; +import { Component } from 'react'; +import { withApollo, graphql, compose } from 'react-apollo'; import styled from 'styled-components'; -import TakeMyMoney from './TakeMyMoney'; -import AddToCart from './AddToCart'; import Pagination from './Pagination'; -import formatMoney from '../lib/formatMoney'; -import makeImage from '../lib/image'; -import slugify from 'slugify'; +import Item from './Item'; import { ALL_ITEMS_QUERY, DELETE_ITEM_MUTATION } from '../queries'; -const Title = styled.h1` - font-size: 10px; -`; +const Title = styled.h1`font-size: 10px;`; const Items = styled.div` display: grid; @@ -22,50 +14,39 @@ const Items = styled.div` grid-gap: 20px; `; -const Item = styled.div` - background: #f3f3f3; - padding: 5px; - img { - width: 100%; - } -`; - class ItemList extends Component { - componentDidMount() { this.prefetchNextItems(this.props.page); } componentWillReceiveProps(nextProps) { // update the next items if the page prop changed - if(this.props.page !== nextProps.page) { + if (this.props.page !== nextProps.page) { this.prefetchNextItems(nextProps.page); } } - prefetchNextItems = (currentPage) => { + prefetchNextItems = currentPage => { const page = currentPage + 1; console.log(`Prefetching Next items! Page ${page}`); this.props.client.query({ query: ALL_ITEMS_QUERY, variables: { - skip: (page * 3) - 3 - } - }) - } + skip: page * 3 - 3, + }, + }); + }; render() { - console.log("CLIENTTT!!", this.props.client); - // 1 if (this.props.allItemsQuery && this.props.allItemsQuery.loading) { - return <div>Loading</div> + return <div>Loading</div>; } // 2 if (this.props.allItemsQuery && this.props.allItemsQuery.error) { - console.log(this.props.allItemsQuery.error) - return <div>Error</div> + console.log(this.props.allItemsQuery.error); + return <div>Error</div>; } // 3 @@ -73,53 +54,12 @@ class ItemList extends Component { return ( <div> - <Pagination page={this.props.page}></Pagination> + <Pagination page={this.props.page} /> <Title>Items For Sale</Title> - <Items> - {itemsToRender.map((item,i) => ( - <Item className="item" key={i}> - { item.image ? <img key={item.image.secret} src={makeImage(item.image)} /> : null } - <h3> - <Link href={{ - pathname: 'item', - query: { - slug: slugify(item.title), - itemId: item.id - }, - }}> - <a>{item.title}</a> - </Link> - </h3> - - <p>{item.description}</p> - <Link href={{ - pathname: '/admin/update', - query: { id: item.id } - }}> - <a>Edit βοΈ</a> - </Link> - - <TakeMyMoney - id={item.id} - amount={item.price} - name={item.title} // the pop-in header title - description={item.description} // the pop-in header subtitle - image={makeImage(item.image)} - > - <button>Buy for {formatMoney(item.price)}</button> - </TakeMyMoney> - - <AddToCart id={item.id}></AddToCart> - <button onClick={() => this.props.removeItemMutation({ variables: { id: item.id }})}>× Delete item</button> - - </Item> - ))} - </Items> - + <Items key={this.props.page}>{itemsToRender.map((item, i) => <Item key={item.id} item={item} />)}</Items> </div> - ) + ); } - } // 1 @@ -127,30 +67,31 @@ class ItemList extends Component { // We export the graphQL HOC - this will fetch the data and inject it into the ItemList compeont via props // Create some Enhancers -const itemsEnahncer = graphql(ALL_ITEMS_QUERY, { name: 'allItemsQuery', options({ page }) { - return { - variables: { - skip: (page * 3) - 3 - }, - } -}}); +const itemEnhancer = graphql(ALL_ITEMS_QUERY, { + name: 'allItemsQuery', + options({ page }) { + return { + variables: { + skip: page * 3 - 3, + }, + }; + }, +}); const deleteItemEnhancer = graphql(DELETE_ITEM_MUTATION, { name: 'removeItemMutation', options: { update: (proxy, { data: { deleteItem } }) => { - // grab the data from our cache const data = proxy.readQuery({ query: ALL_ITEMS_QUERY }); - // filter out the delted item + // filter out the deleted item data.allItems = data.allItems.filter(item => item.id !== deleteItem.id); - // and then "set state" (update cache), so it will update whereever we have used this data on the page + // and then "set state" (update cache), so it will update wherever we have used this data on the page proxy.writeQuery({ query: ALL_ITEMS_QUERY, data }); - }, - } + }, }); -export default withApollo(compose(itemsEnahncer, deleteItemEnhancer)(ItemList)); +export default withApollo(compose(itemEnhancer, deleteItemEnhancer)(ItemList)); diff --git a/components/LoginAuth0.js b/components/LoginAuth0.js index 262d8bd..8691e82 100644 --- a/components/LoginAuth0.js +++ b/components/LoginAuth0.js @@ -1,70 +1,70 @@ -import { Component, PropTypes } from 'react' -import Auth0Lock from 'auth0-lock' -import { graphql, compose } from 'react-apollo' +import { Component, PropTypes } from 'react'; +import Auth0Lock from 'auth0-lock'; +import { graphql, compose } from 'react-apollo'; import { CURRENT_USER_QUERY } from '../queries/index'; class LoginAuth0 extends Component { - - constructor (props) { - super(props) - if(typeof window === 'undefined') return; - const redirectUrl = `http://localhost:3000/signup`; - console.log(redirectUrl); + constructor(props) { + super(props); + if (typeof window === 'undefined') return; + const redirectUrl = `http://localhost:6969/signup`; this._lock = new Auth0Lock('l851ev2q8X48wf56eGLjIWFbMwwbvWPE', 'wesbos.auth0.com', { auth: { redirect: false, - } + }, }); } + componentDidMount() { + console.log('MOUNT'); + this._lock.on('authenticated', authResult => { + window.localStorage.setItem('auth0IdToken', authResult.idToken); + this.props.currentUserQuery.refetch(); + }); + // refech on page load because of the server render + this.props.currentUserQuery.refetch(); + } + logout = () => { window.localStorage.removeItem('auth0IdToken'); this.props.currentUserQuery.refetch(); - } + }; - createUser = () => { + createUser = async () => { const variables = { idToken: window.localStorage.getItem('auth0IdToken'), emailAddress: 'wesbos@gmail.com', - name: 'Hardcoded Wes' + name: 'Hardcoded Wes', }; // TODO - make a createUser function this.props .createUser({ variables }) .then(response => { // this.props.currentUserQuery.refetch(); - this.props.history.replace("/"); + this.props.history.replace('/'); }) .catch(e => { console.error(e); - this.props.history.replace("/"); + this.props.history.replace('/'); }); }; - componentDidMount() { - console.log('MOUNT'); - this._lock.on('authenticated', (authResult) => { - window.localStorage.setItem('auth0IdToken', authResult.idToken) - this.props.currentUserQuery.refetch(); - }) - } - _showLogin = () => { - this._lock.show() - } + this._lock.show(); + }; render() { const { user } = this.props.currentUserQuery; - if ( user ) { - return <button onClick={this.logout}>Log out π</button> + if (user) { + return <button onClick={this.logout}>Log out π</button>; } return ( <div> <button onClick={this._showLogin}>Log in with Auth0 </button> </div> - ) + ); } } const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -export default compose(userEnhancer)(LoginAuth0) +export default compose(userEnhancer)(LoginAuth0); diff --git a/components/Meta.js b/components/Meta.js index 5ecea01..2655a21 100644 --- a/components/Meta.js +++ b/components/Meta.js @@ -1,17 +1,16 @@ import React from 'react'; - import Head from 'next/head'; -import Nav from './Nav'; -export default () => ( +const Meta = () => ( <div> <Head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta charSet="utf-8" /> - <Nav /> <link rel="shortcut icon" href="https://wesbos.com/wp-content/themes/wb2014/i/crown-yellow-small.png" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" /> <title>Sick Fits</title> </Head> </div> ); + +export default Meta; diff --git a/components/Nav.js b/components/Nav.js index ad0b05a..d1f341d 100644 --- a/components/Nav.js +++ b/components/Nav.js @@ -1,10 +1,46 @@ -import Link from 'next/link' +import Link from 'next/link'; +import styled from 'styled-components'; -export default () => ( - <ul> - <Link href="/"><a>Home</a></Link> - <Link href="/signup"><a>Signup</a></Link> - <Link href="/orders"><a>Orders</a></Link> - <Link href="/cart"><a>My Cart</a></Link> - </ul> -) +const StyledUl = styled.ul` + margin: 0; + padding: 0; + display: flex; + li { + display: flex; + flex: 1; + } + a { + padding: 10px; + flex: 1; + text-decoration: none; + text-align: center; + background: rgba(0, 0, 0, 0.2); + color: white; + margin-right: 20px; + &:hover { + background: rgba(0, 0, 0, 0.3); + } + } +`; + +const Nav = () => ( + <StyledUl> + <Link prefetch href="/"> + <a>Home</a> + </Link> + <Link prefetch href="/signup"> + <a>Sign Up</a> + </Link> + <Link prefetch href="/add"> + <a>Add an Item</a> + </Link> + <Link prefetch href="/orders"> + <a>Orders</a> + </Link> + <Link prefetch href="/cart"> + <a>My Cart</a> + </Link> + </StyledUl> +); + +export default Nav; diff --git a/components/Page.js b/components/Page.js index c05eeb7..50a6135 100644 --- a/components/Page.js +++ b/components/Page.js @@ -1,5 +1,5 @@ -import Header from './Header' -import Meta from './Meta' +import Header from './Header'; +import Meta from './Meta'; import Nav from './Nav'; import styled from 'styled-components'; @@ -13,10 +13,10 @@ const StyledPage = styled.div` const Page = ({ children }) => ( <StyledPage className="main"> <Meta /> - <Nav></Nav> + <Nav /> <Header /> - { children } + {children} </StyledPage> -) +); export default Page; diff --git a/components/Pagination.js b/components/Pagination.js index 1b75ed8..fa42e35 100644 --- a/components/Pagination.js +++ b/components/Pagination.js @@ -1,47 +1,43 @@ -import React, { Component } from 'react' -import { graphql, gql, compose } from 'react-apollo' +import React, { Component } from 'react'; +import { graphql, gql, compose } from 'react-apollo'; import { ALL_ITEMS_QUERY } from '../queries'; import withData from '../lib/withData'; -import Link from 'next/link' +import { Link } from '../routes'; -class Pagination extends Component { - render() { - const { loading, error } = this.props.allItemsQuery; +const Pagination = props => { + const { loading, error } = props.allItemsQuery; - if(loading) return <p>Loading Item...</p> + if (loading) return <p>Loading Item...</p>; - const meta = this.props.allItemsQuery._allItemsMeta; - const { page } = this.props; - const pages = Math.floor(meta.count / 3); - return ( - <div> - <p> - Page <strong>{page} </strong> - of - <strong>{pages} </strong> - - - <strong>{meta.count} </strong> - total - </p> + const meta = props.allItemsQuery._allItemsMeta; + const { page } = props; + const pages = Math.floor(meta.count / 3); + return ( + <div> + <p> + Page <strong>{page} </strong> + of + <strong>{pages} </strong> + - + <strong>{meta.count} </strong> + total + </p> - { page > 1 - ? <Link prefetch href={{ pathname: 'items', query: { page: page - 1 }}}><a>βPrev</a></Link> - : null - } + {page > 1 ? ( + <Link prefetch route="items" params={{ page: page - 1 }}> + <a>βPrev</a> + </Link> + ) : null} - { - page <= pages - ? <Link prefetch href={{ pathname: 'items', query: { page: page + 1 }}}><a>Next β</a></Link> - : null - } + {page <= pages ? ( + <Link prefetch route="items" params={{ page: page + 1 }}> + <a>Next β</a> + </Link> + ) : null} + </div> + ); +}; - </div> - ) - } -} - -const ComponentWithMutations = compose( - graphql(ALL_ITEMS_QUERY, { name: 'allItemsQuery' }) -)(Pagination); +const ComponentWithMutations = compose(graphql(ALL_ITEMS_QUERY, { name: 'allItemsQuery' }))(Pagination); export default ComponentWithMutations; diff --git a/components/Search.js b/components/Search.js index afab3e8..63c467e 100644 --- a/components/Search.js +++ b/components/Search.js @@ -1,78 +1,69 @@ -import Downshift from 'downshift' +import Downshift from 'downshift'; import { SEARCH_ITEMS_QUERY } from '../queries'; -import { graphql, compose } from 'react-apollo' +import { graphql, compose } from 'react-apollo'; import makeImage from '../lib/image'; -import Router from 'next/router' +import { Router } from '../routes'; import slugify from 'slugify'; function routeToItem(item) { - Router.push({ - pathname: '/item', - query: { - slug: slugify(item.title), - itemId: item.id - } - }) + Router.pushRoute('item', { + slug: slugify(item.title), + itemId: item.id, + }); } function BasicAutocomplete(props) { const { items, onChange } = props; console.log(props); return ( - <Downshift - onChange={routeToItem} - itemToString={(item) => item.title} - > - {({ - getInputProps, - getItemProps, - isOpen, - inputValue, - selectedItem, - highlightedIndex - }) => ( + <Downshift onChange={routeToItem} itemToString={item => item.title}> + {({ getInputProps, getItemProps, isOpen, inputValue, selectedItem, highlightedIndex }) => ( <div> - <input {...getInputProps({ - placeholder: 'Search For Item', - onChange: (e) => props.refetch({ searchTerm: e.target.value }), - style: { fontSize: '20px', padding: '20px', display: 'block', width: '100%' } - })} /> + <input + {...getInputProps({ + placeholder: 'Search For Item', + onChange: e => props.refetch({ searchTerm: e.target.value }), + style: { fontSize: '20px', padding: '20px', display: 'block', width: '100%' }, + })} + /> {isOpen ? ( <div> - {items - .map((item, index) => ( - <div - {...getItemProps({item})} - key={item.id} - style={{ - backgroundColor: highlightedIndex === index ? '#e8e8e8' : 'white', - borderLeft: highlightedIndex === index ? '10px solid #ffc600' : '10px solid white', - padding: '10px', - display: 'flex', - alignItems: 'center' - }} - > - <img width="50" style={{ 'margin-right': '10px'}} src={makeImage(item.image)} alt={item.title}/> - {item.title} - </div> - ))} + {items.map((item, index) => ( + <div + {...getItemProps({ item })} + key={item.id} + style={{ + backgroundColor: highlightedIndex === index ? '#e8e8e8' : 'white', + borderLeft: highlightedIndex === index ? '10px solid #ffc600' : '10px solid white', + padding: '10px', + display: 'flex', + alignItems: 'center', + }} + > + <img width="50" style={{ 'margin-right': '10px' }} src={makeImage(item.image)} alt={item.title} /> + {item.title} + </div> + ))} </div> ) : null} </div> )} </Downshift> - ) + ); } -const Search = (props) => ( - <BasicAutocomplete - items={props.searchItems.allItems} - onChange={selectedItem => console.log(selectedItem)} - refetch={props.searchItems.refetch} - /> -) +const Search = props => ( + <BasicAutocomplete + items={props.searchItems.allItems} + onChange={selectedItem => console.log(selectedItem)} + refetch={props.searchItems.refetch} + /> +); -const searchEnhancer = graphql(SEARCH_ITEMS_QUERY, { name: 'searchItems', options: { - variables: { searchTerm: 'camo' } -} }); -export default compose(searchEnhancer)(Search) +const searchEnhancer = graphql(SEARCH_ITEMS_QUERY, { + name: 'searchItems', + options: { + variables: { searchTerm: 'camo' }, + }, +}); +export default compose(searchEnhancer)(Search); diff --git a/components/SingleItem.js b/components/SingleItem.js new file mode 100644 index 0000000..297c33d --- /dev/null +++ b/components/SingleItem.js @@ -0,0 +1,36 @@ +import { graphql, compose } from 'react-apollo'; +import { Motion, spring } from 'react-motion'; +import { SINGLE_ITEM_QUERY } from '../queries'; +import makeImage from '../lib/image'; + +const SingleItem = props => { + if (!props.findItem.Item) return <p>Not ready</p>; + console.log(props.findItem.Item); + + if (props.loading) return <p>Loading...</p>; + if (props.error) return <p>Error...</p>; + + const item = props.findItem.Item; + return ( + <div> + <img src={makeImage(item.image)} alt={item.title} /> + <h2>Viewing {item.title}</h2> + + <Motion defaultStyle={{ x: 0 }} style={{ x: spring(100) }}> + {value => <div>{value.x}</div>} + </Motion> + </div> + ); +}; + +const ComponentWithMutations = compose( + graphql(SINGLE_ITEM_QUERY, { + name: 'findItem', + // This comes from Props + options: ({ id }) => ({ + variables: { id }, + }), + }) +)(SingleItem); + +export default ComponentWithMutations; |
