diff options
| author | Wes Bos <wesbos@gmail.com> | 2017-09-15 14:26:13 -0400 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2017-09-15 14:26:13 -0400 |
| commit | 1450e579c67e3d969cb099dfe6c1cefd1e313fb5 (patch) | |
| tree | 5c255968082c16452f1dc8245dbbb890f91dce87 /components | |
| parent | 7af74ff8988961c988f03c95961b70c2918521ae (diff) | |
yep
Diffstat (limited to 'components')
| -rw-r--r-- | components/AddToCart.js | 39 | ||||
| -rw-r--r-- | components/Cart.js | 41 | ||||
| -rw-r--r-- | components/CartList.js | 74 | ||||
| -rw-r--r-- | components/Header.js | 10 | ||||
| -rw-r--r-- | components/Item.js | 33 | ||||
| -rw-r--r-- | components/Items.js | 122 | ||||
| -rw-r--r-- | components/LoginAuth0.js | 20 | ||||
| -rw-r--r-- | components/Meta.js | 11 | ||||
| -rw-r--r-- | components/Nav.js | 5 | ||||
| -rw-r--r-- | components/Page.js | 9 | ||||
| -rw-r--r-- | components/Pagination.js | 47 | ||||
| -rw-r--r-- | components/Search.js | 78 | ||||
| -rw-r--r-- | components/UpdateItem.js | 5 |
13 files changed, 434 insertions, 60 deletions
diff --git a/components/AddToCart.js b/components/AddToCart.js new file mode 100644 index 0000000..0c40efe --- /dev/null +++ b/components/AddToCart.js @@ -0,0 +1,39 @@ +import { Component } from 'react'; +import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY } from '../queries'; +import { graphql, compose } from 'react-apollo'; + +class AddToCart extends Component { + componentDidMount() { + this.props.currentUserQuery.refetch(); + } + + addToCart = async () => { + const res = await this.props.addToCart({ + 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); + return ( + <div> + + { + cartIds.includes(this.props.id) + ? <button>Added to cart β
</button> + : <button onClick={this.addToCart}>Add To Cart π</button> + } + </div> + ) + } +} + +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); +const createOrderEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart' }); +export default compose(userEnhancer, createOrderEnhancer)(AddToCart); diff --git a/components/Cart.js b/components/Cart.js new file mode 100644 index 0000000..57a00fc --- /dev/null +++ b/components/Cart.js @@ -0,0 +1,41 @@ +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"; + +const cartStyles = styled.div` + background: white; + padding: 20px; +`; + +class Cart extends Component { + componentDidMount() { + // 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); + } + + render() { + // Check for loading state.. + 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> + ); + } +} + +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: "currentUserQuery" }); +export default compose(userEnhancer)(Cart); diff --git a/components/CartList.js b/components/CartList.js new file mode 100644 index 0000000..d0456aa --- /dev/null +++ b/components/CartList.js @@ -0,0 +1,74 @@ +import { Component } from 'react'; +import withData from '../lib/withData'; +import Items from '../components/Items'; +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 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'; + +class CartList extends Component { + componentDidMount() { + setTimeout(this.props.currentUserQuery.refetch, 1); + } + render() { + + if(this.props.loading) { + return <p>Loading...</p> + } + + if(this.props.error) { + return <p>Error...</p> + } + + if(!has(this.props, 'currentUserQuery.user.cart')) { + return <p>Don't have it yet!</p> + } + + const cart = this.props.currentUserQuery.user.cart; + const userId = this.props.currentUserQuery.user.id; + + const total = cart.reduce((a, b) => a + b.price, 0); + return ( + <div> + <h1>{cart.length} Items</h1> + <ul> + {cart.map(item => ( + <li key={item.id}> + {item.title} + <button onClick={() => this.props.removeFromCart({ variables: { + userId, + itemId: item.id + }})}>× Delete</button> + </li> + ))} + </ul> + <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) diff --git a/components/Header.js b/components/Header.js index 52aeef9..60d0621 100644 --- a/components/Header.js +++ b/components/Header.js @@ -1,6 +1,9 @@ import { Component } from 'react' import { graphql, compose } from 'react-apollo' import { CURRENT_USER_QUERY } from '../queries'; +import Cart from './Cart'; +import Login from './LoginAuth0'; +import Search from './Search'; class Header extends Component { @@ -12,13 +15,14 @@ class Header extends Component { } render() { - console.log(this.props.currentUserQuery.user); const user = this.props.currentUserQuery.user || {}; const { email = '' } = user; - return ( <div> - <p>{email} I'm the header!</p> + <p>Signed in as <strong>{email}</strong></p> + <Login></Login> + <Cart></Cart> + <Search></Search> </div> ) } diff --git a/components/Item.js b/components/Item.js new file mode 100644 index 0000000..66ca66f --- /dev/null +++ b/components/Item.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import { graphql, gql, compose } from 'react-apollo' +import { SINGLE_ITEM_QUERY } from '../queries'; +import withData from '../lib/withData'; + +class Item extends Component { + render() { + if(!this.props.findItem.Item) return <p>Not ready</p> + console.log(this.props.findItem.Item); + + if(this.props.loading) return <p>Loading...</p> + if(this.props.error) return <p>Error...</p> + + const item = this.props.findItem.Item; + return ( + <div> + <h2>Viewing {item.title}</h2> + </div> + ) + } +} + +const ComponentWithMutations = compose( + graphql(SINGLE_ITEM_QUERY, { + name: 'findItem', + // This comes from Props + options: ({ id }) => ({ + variables: { id } + }) + }) +)(Item); + +export default ComponentWithMutations; diff --git a/components/Items.js b/components/Items.js index 7d07643..d212ead 100644 --- a/components/Items.js +++ b/components/Items.js @@ -1,29 +1,30 @@ import { Component } from 'react' -import { graphql, compose } from 'react-apollo' +import { withApollo, graphql, compose } from 'react-apollo' import UpdateItem from './UpdateItem'; import Link from 'next/link'; 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 { ALL_ITEMS_QUERY, DELETE_ITEM_MUTATION } from '../queries'; const Title = styled.h1` - font-size: 50px; + font-size: 10px; `; - - const Items = styled.div` display: grid; - grid-template-columns: repeat(4, calc(25% - 20px)); + grid-template-columns: repeat(4, calc(33% - 20px)); grid-gap: 20px; `; const Item = styled.div` background: #f3f3f3; - padding: 20px; + padding: 5px; img { width: 100%; } @@ -31,51 +32,91 @@ const Item = styled.div` 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) { + this.prefetchNextItems(nextProps.page); + } + } + + 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 + } + }) + } + render() { + console.log("CLIENTTT!!", this.props.client); // 1 - if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { + if (this.props.allItemsQuery && this.props.allItemsQuery.loading) { return <div>Loading</div> } // 2 - if (this.props.allLinksQuery && this.props.allLinksQuery.error) { - console.log(this.props.allLinksQuery.error) + if (this.props.allItemsQuery && this.props.allItemsQuery.error) { + console.log(this.props.allItemsQuery.error) return <div>Error</div> } // 3 - const itemsToRender = this.props.allLinksQuery.allItems + const itemsToRender = this.props.allItemsQuery.allItems; return ( - <Items> + <div> + <Pagination page={this.props.page}></Pagination> <Title>Items For Sale</Title> - {itemsToRender.map((item,i) => ( - <Item className="item" key={i}> - { item.image ? <img src={makeImage(item.image)} /> : null } - <h3>{item.title}</h3> - <p>{item.description}</p> - <Link href={{ - pathname: '/admin/update', - query: { id: item.id } - }}> - <a>Edit {item.id}</a> - </Link> + <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> - <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> - <button onClick={() => this.props.removeItemMutation({ variables: { id: item.id }})}>× Delete item</button> + </Item> + ))} + </Items> - </Item> - ))} - </Items> + </div> ) } @@ -86,7 +127,14 @@ 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: 'allLinksQuery' }); +const itemsEnahncer = graphql(ALL_ITEMS_QUERY, { name: 'allItemsQuery', options({ page }) { + return { + variables: { + skip: (page * 3) - 3 + }, + } +}}); + const deleteItemEnhancer = graphql(DELETE_ITEM_MUTATION, { name: 'removeItemMutation', options: { @@ -105,4 +153,4 @@ const deleteItemEnhancer = graphql(DELETE_ITEM_MUTATION, { } }); -export default compose(itemsEnahncer, deleteItemEnhancer)(ItemList) +export default withApollo(compose(itemsEnahncer, deleteItemEnhancer)(ItemList)); diff --git a/components/LoginAuth0.js b/components/LoginAuth0.js index d5293b5..262d8bd 100644 --- a/components/LoginAuth0.js +++ b/components/LoginAuth0.js @@ -1,5 +1,7 @@ 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 { @@ -15,9 +17,14 @@ class LoginAuth0 extends Component { }); } + logout = () => { + window.localStorage.removeItem('auth0IdToken'); + this.props.currentUserQuery.refetch(); + } + createUser = () => { const variables = { - idToken: window.localStorage.getItem("auth0IdToken"), + idToken: window.localStorage.getItem('auth0IdToken'), emailAddress: 'wesbos@gmail.com', name: 'Hardcoded Wes' }; @@ -25,6 +32,7 @@ class LoginAuth0 extends Component { this.props .createUser({ variables }) .then(response => { + // this.props.currentUserQuery.refetch(); this.props.history.replace("/"); }) .catch(e => { @@ -36,9 +44,8 @@ class LoginAuth0 extends Component { componentDidMount() { console.log('MOUNT'); this._lock.on('authenticated', (authResult) => { - console.log('HIIIIIIII') window.localStorage.setItem('auth0IdToken', authResult.idToken) - console.log('Done!', authResult); + this.props.currentUserQuery.refetch(); }) } @@ -47,6 +54,10 @@ class LoginAuth0 extends Component { } render() { + const { user } = this.props.currentUserQuery; + if ( user ) { + return <button onClick={this.logout}>Log out π</button> + } return ( <div> <button onClick={this._showLogin}>Log in with Auth0 </button> @@ -55,4 +66,5 @@ class LoginAuth0 extends Component { } } -export default LoginAuth0; +const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); +export default compose(userEnhancer)(LoginAuth0) diff --git a/components/Meta.js b/components/Meta.js index d851946..5ecea01 100644 --- a/components/Meta.js +++ b/components/Meta.js @@ -1,5 +1,6 @@ -import Head from 'next/head' -import Router from 'next/router' +import React from 'react'; + +import Head from 'next/head'; import Nav from './Nav'; export default () => ( @@ -7,10 +8,10 @@ export default () => ( <Head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta charSet="utf-8" /> - <Nav/> + <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"/> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" /> <title>Sick Fits</title> </Head> </div> -) +); diff --git a/components/Nav.js b/components/Nav.js index 2e32a1c..ad0b05a 100644 --- a/components/Nav.js +++ b/components/Nav.js @@ -3,7 +3,8 @@ import Link from 'next/link' 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="/signup"><a>Signup</a></Link> + <Link href="/orders"><a>Orders</a></Link> + <Link href="/cart"><a>My Cart</a></Link> </ul> ) diff --git a/components/Page.js b/components/Page.js index 1b07fe6..c05eeb7 100644 --- a/components/Page.js +++ b/components/Page.js @@ -1,6 +1,5 @@ import Header from './Header' import Meta from './Meta' -import withData from '../lib/withData'; import Nav from './Nav'; import styled from 'styled-components'; @@ -14,12 +13,10 @@ const StyledPage = styled.div` const Page = ({ children }) => ( <StyledPage className="main"> <Meta /> - <Header /> <Nav></Nav> - <div> - { children } - </div> + <Header /> + { children } </StyledPage> ) -export default withData(Page); +export default Page; diff --git a/components/Pagination.js b/components/Pagination.js new file mode 100644 index 0000000..1b75ed8 --- /dev/null +++ b/components/Pagination.js @@ -0,0 +1,47 @@ +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' + +class Pagination extends Component { + render() { + const { loading, error } = this.props.allItemsQuery; + + 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> + + { page > 1 + ? <Link prefetch href={{ pathname: 'items', query: { page: page - 1 }}}><a>βPrev</a></Link> + : null + } + + { + page <= pages + ? <Link prefetch href={{ pathname: 'items', query: { page: page + 1 }}}><a>Next β</a></Link> + : null + } + + </div> + ) + } +} + +const ComponentWithMutations = compose( + graphql(ALL_ITEMS_QUERY, { name: 'allItemsQuery' }) +)(Pagination); + +export default ComponentWithMutations; diff --git a/components/Search.js b/components/Search.js new file mode 100644 index 0000000..afab3e8 --- /dev/null +++ b/components/Search.js @@ -0,0 +1,78 @@ +import Downshift from 'downshift' +import { SEARCH_ITEMS_QUERY } from '../queries'; +import { graphql, compose } from 'react-apollo' +import makeImage from '../lib/image'; +import Router from 'next/router' +import slugify from 'slugify'; + +function routeToItem(item) { + Router.push({ + pathname: '/item', + query: { + 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 + }) => ( + <div> + <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> + ))} + </div> + ) : null} + </div> + )} + </Downshift> + ) +} + +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) diff --git a/components/UpdateItem.js b/components/UpdateItem.js index 0edad06..a3b45a8 100644 --- a/components/UpdateItem.js +++ b/components/UpdateItem.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import { graphql, gql, compose } from 'react-apollo' -import { SINGLE_LINK_QUERY, UPDATE_LINK_MUTATION } from '../queries'; +import { SINGLE_ITEM_QUERY, UPDATE_LINK_MUTATION } from '../queries'; class UpdateLink extends Component { @@ -66,7 +66,7 @@ class UpdateLink extends Component { const ComponentWithMutations = compose( // First, query for getting the link - graphql(SINGLE_LINK_QUERY, { + graphql(SINGLE_ITEM_QUERY, { name: 'findItem', options: ({ id }) => ({ variables: { id } @@ -76,5 +76,4 @@ const ComponentWithMutations = compose( graphql(UPDATE_LINK_MUTATION, { name: 'updateItem' }) )(UpdateLink); - export default ComponentWithMutations; |
