diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-02-15 16:59:58 -0500 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-02-15 16:59:58 -0500 |
| commit | 8f2c595eca30e2d7138c8e8d6685774a8f44e55d (patch) | |
| tree | 3408b9c8eb3b0ea43cb72e13e0bd6393ab5bac61 /frontend | |
| parent | a09b01abf7c03e6b7e43f2175e5a34501808821b (diff) | |
omg
Diffstat (limited to 'frontend')
31 files changed, 552 insertions, 397 deletions
diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index a92f73f..4718ffd 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -1,3 +1,3 @@ { - "eslint.enable": false + "eslint.enable": true }
\ No newline at end of file diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js index 5a11513..6cab321 100644 --- a/frontend/components/AddToCart.js +++ b/frontend/components/AddToCart.js @@ -7,7 +7,6 @@ import { CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries'; import { removeFromCartEnhancer, userEnhancer, addtoCartEnhancer } from '../enhancers/enhancers'; console.log(addtoCartEnhancer); -import makeImage from '../lib/image'; const JumpImg = styled.img` border: 0 solid black; diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js index 72c34ae..78e0d88 100644 --- a/frontend/components/Cart.js +++ b/frontend/components/Cart.js @@ -1,9 +1,14 @@ import { Component } from 'react'; import { graphql, compose } from 'react-apollo'; import styled from 'styled-components'; -import { networkEnhancer, userEnhancer, uiEnhancer } from '../enhancers/enhancers'; +import Title from './styles/Title'; +import { updateUIEnhancer, userEnhancer, uiEnhancer } from '../enhancers/enhancers'; import RemoveFromCart from './RemoveFromCart'; import TakeMyMoney from './TakeMyMoney'; +import ToggleCart from './ToggleCart'; +import formatMoney from '../lib/formatMoney'; + +import CartItem from './CartItem'; const CartStyles = styled.div` padding: 20px; @@ -13,75 +18,104 @@ const CartStyles = styled.div` height: 100%; top: 0; right: 0; + width: 40%; + min-width: 500px; bottom: 0; transform: translateX(100%); transition: all 0.3s; box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.2); z-index: 5; + display: grid; + grid-template-rows: auto 1fr auto; ${props => props.open && `transform: translateX(0);`}; - .toggleOpen { - position: absolute; - top: 0; - left: 0; - transform: translateX(-100%); + header { + border-bottom: 5px solid ${props => props.theme.black}; + margin-bottom: 2rem; + padding-bottom: 2rem; + } + footer { + border-top: 10px double ${props => props.theme.black}; + margin-top: 2rem; + padding-top: 2rem; + display: grid; + grid-template-columns: auto auto; + align-items: center; + font-size: 3rem; + font-weight: 900; + p { + margin: 0; + } } `; +const CartUl = styled.ul` + margin: 0; + padding: 0; + list-style: none; + overflow: scroll; +`; + +const Supreme = styled.h3` + background: ${props => props.theme.red}; + color: white; + display: inline-block; + padding: 4px 5px; + transform: skew(-3deg); + margin: 0; + font-size: 4rem; +`; + +const CloseButton = styled.button` + background: black; + color: white; + font-size: 3rem; + border: 0; + position: absolute; + z-index: 2; + right: 0; +`; + class Cart extends Component { - state = { - open: true, - }; componentDidMount() { - console.log('refetching..'); - setTimeout(this.props.currentUser.refetch, 10); + // TODO listen for escape + } + + componentWillUnmount() { + // TODO unlisten for escape } - toggleOpen = () => { - this.setState({ open: !this.state.open }); - }; - handleClick = () => { - console.log('CLICK'); - this.props.updateNetworkStatus(!this.props.ui.networkStatus.isConnected); - }; render() { - if (!this.props.currentUser.me) { - return null; - } const { me } = this.props.currentUser; + if (!me) return null; + const itemCount = me.cart.length; + const quantityCount = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); + const totalPrice = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity * cartItem.item.price, 0); return ( - <CartStyles className="cart" open={this.state.open}> - <button onClick={this.handleClick}> - Toggle Network Status:: {this.props.ui.networkStatus.isConnected.toString()} - </button> - <button className="toggleOpen" onClick={this.toggleOpen}> - Open Cart - </button> - - <p>🛒: {me.cart.length} Items</p> - <ul> - {me.cart.map(cartItem => ( - <li key={cartItem.id}> - <strong> - {cartItem.quantity} of {cartItem.item.title} - </strong> - <RemoveFromCart id={cartItem.id} /> - </li> - ))} - </ul> + <CartStyles open={this.props.ui.uiData.isCartOpen}> + <header> + <ToggleCart + render={toggle => ( + <CloseButton title="close" onClick={toggle}> + × + </CloseButton> + )} + /> + <Supreme>Your Cart.</Supreme> + <p> + You have {itemCount} item{itemCount === 1 ? '' : 's'} in your cart. S{'I'.repeat(quantityCount)}CK + </p> + </header> - <TakeMyMoney> - <hr /> - <button>Checkout!!!</button> - </TakeMyMoney> - {/* Theren - {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)} /> */} + <CartUl>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</CartUl> + <footer> + <p>{formatMoney(totalPrice)}</p> + <TakeMyMoney> + <button>Checkout</button> + </TakeMyMoney> + </footer> </CartStyles> ); } } -export default compose(userEnhancer, networkEnhancer, uiEnhancer)(Cart); +export default compose(userEnhancer, uiEnhancer)(Cart); diff --git a/frontend/components/CartItem.js b/frontend/components/CartItem.js new file mode 100644 index 0000000..83c948e --- /dev/null +++ b/frontend/components/CartItem.js @@ -0,0 +1,39 @@ +import formatMoney from '../lib/formatMoney'; +import RemoveFromCart from './RemoveFromCart'; +import styled from 'styled-components'; + +const CartItemStyles = styled.li` + padding: 1rem 0; + border-bottom: 1px solid ${props => props.theme.lightgrey}; + display: grid; + align-items: center; + grid-template-columns: auto 1fr auto; + img { + margin-right: 10px; + } + h3 { + margin: 0; + } + p { + margin: 0; + } +`; + +const CartItem = ({ cartItem }) => ( + <CartItemStyles key={cartItem.id}> + <img width="100" src={cartItem.item.image} alt={cartItem.item.title} /> + <div className="cart-item-details"> + <h3>{cartItem.item.title}</h3> + <p> + {formatMoney(cartItem.quantity * cartItem.item.price)} + {' — '} + <em> + {cartItem.quantity} × {formatMoney(cartItem.item.price)} each + </em> + </p> + </div> + <RemoveFromCart id={cartItem.id} /> + </CartItemStyles> +); + +export default CartItem; diff --git a/frontend/components/CartList.js b/frontend/components/CartList.js index bf08f87..1a44012 100644 --- a/frontend/components/CartList.js +++ b/frontend/components/CartList.js @@ -1,21 +1,11 @@ // I THINK THIS CAN BE DELETED 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, GET_CART_STATE } from '../queries'; -import { graphql, compose } from 'react-apollo'; -import has from 'lodash.has'; -import get from 'lodash.get'; +import { compose } from 'react-apollo'; import styled from 'styled-components'; +import PropTypes from 'prop-types'; import formatMoney from '../lib/formatMoney'; -import makeImage from '../lib/image'; import TakeMyMoney from './TakeMyMoney'; -import { removeFromCartEnhancer } from '../enhancers/enhancers'; -import { CURRENT_USER_QUERY } from '../queries'; -import PropTypes from 'prop-types'; +import { removeFromCartEnhancer, userEnhancer, uiEnhancer } from '../enhancers/enhancers'; const CartStyles = styled.div` padding: 20px; @@ -44,15 +34,6 @@ class CartList extends Component { client: PropTypes.object, }; - componentDidMount() { - setTimeout(this.props.currentUserQuery.refetch, 1); - // Set the cart state to be closed - this.context.client.writeQuery({ - query: GET_CART_STATE, - data: { ui: { isCartOpen: true, __typename: 'Network' } }, - }); - } - toggleOpen = () => { this.props.uiQuery.updateQuery(() => ({ ui: { isCartOpen: false, __typename: 'Network' } })); }; @@ -66,15 +47,13 @@ class CartList extends Component { return <p>Error....</p>; } - // if (!has(this.props, 'currentUserQuery.user.cart')) { // return <p>Don't have it yet!</p>; // } // OMG Clean this up - let { user = {} } = this.props.currentUserQuery || {}; + if (!user) user = {}; const cart = user.cart || []; const userId = user.id; - // const userId = this.props.currentUserQuery.user.id; const total = cart.reduce((a, b) => a + b.price, 0); return ( @@ -83,7 +62,6 @@ class CartList extends Component { Open Cart </button> <h1>{cart.length} Items</h1> - <ul> {cart.map(item => ( <li key={item.id}> @@ -110,6 +88,4 @@ class CartList extends Component { } } -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -const uiEnhancer = graphql(GET_CART_STATE, { name: 'uiQuery' }); export default compose(userEnhancer, removeFromCartEnhancer, uiEnhancer)(CartList); diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js index 47e98ec..0d43c68 100644 --- a/frontend/components/CreateItem.js +++ b/frontend/components/CreateItem.js @@ -2,7 +2,6 @@ import React, { Component } from 'react'; import { graphql, gql } from 'react-apollo'; import { ALL_ITEMS_QUERY, CREATE_ITEM_MUTATION } from '../queries'; import ErrorMessage from './ErrorMessage'; -import makeImage from '../lib/image'; import { fileEndpoint } from '../config'; class CreateLink extends Component { @@ -102,7 +101,7 @@ class CreateLink extends Component { type="text" placeholder="The desc for this item" /> - <button disabledx={!this.state.loading} type="submit"> + <button disabled={!this.state.loading} type="submit"> Submit </button> </form> diff --git a/frontend/components/Header.js b/frontend/components/Header.js index aa3c3d8..0183e4a 100644 --- a/frontend/components/Header.js +++ b/frontend/components/Header.js @@ -1,40 +1,66 @@ import { Component } from 'react'; -import { graphql, compose } from 'react-apollo'; +import { compose } from 'react-apollo'; import { userEnhancer } from '../enhancers/enhancers'; -import Cart from './Cart'; -import Login from './LoginAuth0'; -import Search from './Search'; import NProgress from 'nprogress'; import Router from 'next/router'; +import styled from 'styled-components'; +import Link from 'next/link'; import Signout from './Signout'; +import Cart from './Cart'; +import Search from './Search'; +import Nav from './Nav'; Router.onRouteChangeStart = url => { - console.log(`Loading: ${url}`); NProgress.start(); }; Router.onRouteChangeComplete = () => NProgress.done(); Router.onRouteChangeError = () => NProgress.done(); -class Header 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 - // setTimeout(this.props.currentUserQuery.refetch, 1); - console.log(this.props.currentUser); +const StyledHeader = styled.header` + .bar { + border-bottom: 10px solid ${props => props.theme.black}; + display: grid; + grid-template-columns: auto 1fr; + justify-content: space-between; + align-items: stretch; + } + .sub-bar { + display: grid; + grid-template-columns: 1fr auto; + border-bottom: 1px solid ${props => props.theme.lightgrey}; } +`; - render() { - return ( - <div> - {this.props.currentUser.me ? this.props.currentUser.me.email : 'Not Signed in'} - <Signout /> - <Cart /> - {/* <Search /> */} - </div> - ); +const Logo = styled.h1` + font-size: 4rem; + margin-left: 2rem; + transform: skew(-5deg); + a { + padding: 1rem 1.5rem; + background: ${props => props.theme.red}; + color: white; + letter-spacing: -2px; + text-transform: uppercase; } -} +`; + +const Header = props => ( + <StyledHeader> + <div className="bar"> + <Logo> + <Link href="/"> + <a>Sick Fits!</a> + </Link> + </Logo> + <Nav /> + </div> + <div className="sub-bar"> + <Search /> + <Signout /> + </div> + <Cart /> + </StyledHeader> +); export default compose(userEnhancer)(Header); // export default Header; diff --git a/frontend/components/Item.js b/frontend/components/Item.js index 42a6c87..87fb0b9 100644 --- a/frontend/components/Item.js +++ b/frontend/components/Item.js @@ -1,31 +1,74 @@ import React from 'react'; +import Title from './styles/Title'; import styled from 'styled-components'; import slugify from 'slugify'; import { compose } from 'react-apollo'; import { Link } from '../routes'; import AddToCart from './AddToCart'; -import makeImage from '../lib/image'; import formatMoney from '../lib/formatMoney'; import { removeItemMutation } from '../enhancers/enhancers'; const Item = styled.div` - background: #f3f3f3; - padding: 5px; + background: white; + border: 1px solid ${props => props.theme.offWhite}; + box-shadow: 0 12px 24px 0 rgba(0, 0, 0, 0.09); + position: relative; + display: grid; + align-content: start; + /* grid-template-rows: repeat(auto-fit, minmax(100px,)); */ + grid-auto-rows: fit-content; img { width: 100%; } + p { + font-size: 14px; + line-height: 2; + font-weight: 600; + padding: 0 3rem; + font-size: 1.5rem; + } + .buttonList { + display: grid; + border-top: 1px solid ${props => props.theme.lightgrey}; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); + grid-gap: 1px; + background: ${props => props.theme.lightgrey}; + align-self: end; + & > * { + background: white; + border: 0; + text-align: center; + font-size: 1rem; + padding: 1rem; + } + } +`; + +const PriceTag = styled.span` + background: ${props => props.theme.red}; + transform: rotate(2deg); + color: white; + font-weight: 600; + padding: 5px; + font-size: 3rem; + display: inline-block; + position: absolute; + top: -3px; + right: -3px; `; class ItemComponent extends React.Component { removeItem = () => { - this.props.removeItem({ variables: { id: this.props.item.id } }); + if (confirm('Are you sure you want to delete this item?')) { + this.props.removeItem({ variables: { id: this.props.item.id } }); + } }; render() { const item = this.props.item; return ( <Item key={item.id}> {item.image ? <img src={item.image} alt={item.title} /> : null} - <h3> + <Title> <Link route="item" params={{ @@ -35,20 +78,24 @@ class ItemComponent extends React.Component { > <a>{item.title}</a> </Link> - </h3> + </Title> + + <PriceTag>{formatMoney(item.price)}</PriceTag> <p>{item.description}</p> - <Link - href={{ - pathname: '/admin/update', - query: { id: item.id }, - }} - > - <a>Edit ✏️</a> - </Link> - <AddToCart id={item.id} /> - <button onClick={this.removeItem}>× Delete item</button> + <div className="buttonList"> + <Link + href={{ + pathname: '/admin/update', + query: { id: item.id }, + }} + > + <a>Edit ✏️</a> + </Link> + <AddToCart id={item.id} /> + <button onClick={this.removeItem}>× Delete item</button> + </div> </Item> ); } diff --git a/frontend/components/Items.js b/frontend/components/Items.js index a3f98c9..6206592 100644 --- a/frontend/components/Items.js +++ b/frontend/components/Items.js @@ -13,8 +13,10 @@ const Title = styled.h1` const Items = styled.div` display: grid; - grid-template-columns: repeat(4, 1fr); - grid-gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-gap: 60px; + max-width: ${props => props.theme.maxWidth}; + margin: 0 auto; `; class ItemList extends Component { diff --git a/frontend/components/LoginAuth0.js b/frontend/components/LoginAuth0.js deleted file mode 100644 index b55ae4a..0000000 --- a/frontend/components/LoginAuth0.js +++ /dev/null @@ -1,76 +0,0 @@ -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`; - this._lock = new Auth0Lock('r6D64Waoq7roAnv8GT04dnn1tpq9PAqu', 'wesbos.auth0.com', { - auth: { - redirect: false, - }, - }); - } - - componentDidMount() { - this._lock.on('authenticated', authResult => { - console.log(authResult); - window.localStorage.setItem('auth0IdToken', authResult.idToken); - console.log('Im baaaack!'); - this.props.currentUserQuery.refetch(); - }); - - this._lock.on('authorization_error', err => { - console.error(err); - }); - - // refech on page load because of the server render - this.props.currentUserQuery.refetch(); - } - - logout = () => { - window.localStorage.removeItem('auth0IdToken'); - this.props.currentUserQuery.refetch(); - }; - - createUser = async () => { - const variables = { - idToken: window.localStorage.getItem('auth0IdToken'), - emailAddress: 'wesbos@gmail.com', - name: 'Hardcoded Wes', - }; - // TODO - make a createUser function - this.props - .createUser({ variables }) - .then(response => { - // this.props.currentUserQuery.refetch(); - this.props.history.replace('/'); - }) - .catch(e => { - console.error(e); - this.props.history.replace('/'); - }); - }; - - _showLogin = () => { - this._lock.show(); - }; - - 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> - </div> - ); - } -} - -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); -export default compose(userEnhancer)(LoginAuth0); diff --git a/frontend/components/Meta.js b/frontend/components/Meta.js index d7a2e17..bee5a69 100644 --- a/frontend/components/Meta.js +++ b/frontend/components/Meta.js @@ -12,8 +12,7 @@ const Meta = () => ( } `} <meta charSet="utf-8" /> - <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="shortcut icon" href="/static/favicon.png" /> <link rel="stylesheet" type="text/css" href="/static/nprogress.css" /> <title>Sick Fits</title> diff --git a/frontend/components/Nav.js b/frontend/components/Nav.js index 25965c2..9793087 100644 --- a/frontend/components/Nav.js +++ b/frontend/components/Nav.js @@ -1,49 +1,95 @@ -import Link from "next/link"; -import styled from "styled-components"; +import Link from 'next/link'; +import styled from 'styled-components'; +import { userEnhancer } from '../enhancers/enhancers'; +import { compose } from 'react-apollo'; +import ToggleCart from './ToggleCart'; const StyledUl = styled.ul` margin: 0; padding: 0; display: flex; - li { + justify-self: end; + a, + button { + padding: 0 30px; display: flex; - flex: 1; + align-items: center; + position: relative; + text-transform: uppercase; + font-weight: 900; + font-size: 2rem; + background: none; + border: 0; + &:before { + content: ''; + width: 1px; + background: ${props => props.theme.lightgrey}; + height: 100%; + left: 0; + position: absolute; + transform: rotate(20deg); + top: 0; + bottom: 0; + } + &:after { + height: 2px; + background: red; + content: ''; + width: 0; + position: absolute; + transform: translateX(-50%); + transition: width 0.6s; + transition-timing-function: cubic-bezier(1, -0.65, 0, 2.31); + left: 50%; + margin-top: 2rem; + } + &:hover, + &:focus { + outline: none; + &:after { + width: calc(100% - 60px); + } + } } - a { - padding: 10px; - flex: 1; - text-decoration: none; - text-align: center; - background: rgba(0, 0, 0, 0.2); + .cart-count { + background: ${props => props.theme.red}; color: white; - margin-right: 20px; - &:hover { - background: rgba(0, 0, 0, 0.3); - } + border-radius: 50%; + padding: 5px; + margin-left: 1rem; + font-weight: 100; + font-feature-settings: 'tnum'; + font-variant-numeric: tabular-nums; } `; -const Nav = () => ( +const Nav = ({ currentUser }) => ( <StyledUl> - <Link prefetch href="/"> - <a>Home</a> - </Link> <Link prefetch href="/items"> - <a>Just Items</a> + <a>Shop</a> + </Link> + <Link prefetch href="/add"> + <a>Sell</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> + {currentUser.me ? ( + <ToggleCart + render={toggle => ( + <button onClick={toggle}> + My Cart + <span className="cart-count"> + {currentUser.me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)} + </span> + </button> + )} + /> + ) : null} </StyledUl> ); -export default Nav; +export default compose(userEnhancer)(Nav); diff --git a/frontend/components/OrderList.js b/frontend/components/OrderList.js index e478984..5e32ba2 100644 --- a/frontend/components/OrderList.js +++ b/frontend/components/OrderList.js @@ -3,14 +3,12 @@ import { USER_ORDERS_QUERY } from '../queries'; import { graphql, compose } from 'react-apollo'; import { format, formatDistance } from 'date-fns'; import formatMoney from '../lib/formatMoney'; -import makeImage from '../lib/image'; class OrderList extends Component { componentDidMount() { this.props.userOrdersQuery.refetch(); } render() { - console.log(this.props.userOrdersQuery); if (this.props.loading) { return <p>Loading...</p>; } @@ -23,7 +21,6 @@ class OrderList extends Component { if (!orders || !orders.length) return <p>No Orders Yet!</p>; - console.log(orders); return ( <div> <h2>You have {orders.length} Orders</h2> diff --git a/frontend/components/Page.js b/frontend/components/Page.js index 93cd7c9..66c65e6 100644 --- a/frontend/components/Page.js +++ b/frontend/components/Page.js @@ -1,44 +1,56 @@ -import styled from 'styled-components'; +import styled, { ThemeProvider, injectGlobal } from 'styled-components'; import Header from './Header'; import Meta from './Meta'; import Nav from './Nav'; import CartList from './CartList'; import { Component } from 'react'; -class ErrorBoundary extends Component { - state = { - error: null, - errorInfo: null, - }; - componentDidCatch(error, errorInfo) { - console.log('Caught an erorrror!'); - this.setState({ error, errorInfo }); +// Global Style + +const theme = { + red: '#FF0000', + black: '#393939', + grey: '#3A3A3A', + lightgrey: '#E1E1E1', + offWhite: '#EDEDED', + maxWidth: '1300px', +}; + +const globals = injectGlobal` + html { + box-sizing: border-box; + font-size: 10px; + } + body { + font-family: 'radnika next', sans-serif; + padding: 0; + background-color: #ffffff; + background: blue; + margin: 0; + font-size: 1.5rem; } - render() { - if (this.state.error) { - return <p>Shit! AN error! {this.state.error.message}</p>; - } - return this.props.children; + *, *:before, *:after { + box-sizing: inherit; } -} + a { + color: ${theme.black}; + text-decoration: none; + } +`; const StyledPage = styled.div` - font-family: sans-serif; - color: #303030; - background: #efc600; - padding: 100px; + color: ${props => props.theme.black}; + background: white; `; const Page = ({ children }) => ( - <StyledPage className="main"> - <ErrorBoundary> + <ThemeProvider theme={theme}> + <StyledPage className="main"> <Meta /> - <Nav /> <Header /> - {/* <CartList /> */} {children} - </ErrorBoundary> - </StyledPage> + </StyledPage> + </ThemeProvider> ); export default Page; diff --git a/frontend/components/Pagination.js b/frontend/components/Pagination.js index 2cb5080..d00778b 100644 --- a/frontend/components/Pagination.js +++ b/frontend/components/Pagination.js @@ -1,15 +1,10 @@ -import React, { Component } from 'react'; -import { graphql, gql, compose } from 'react-apollo'; -import { ALL_ITEMS_QUERY } from '../queries'; -import withData from '../lib/withData'; +import React from 'react'; +import { compose } from 'react-apollo'; import { Link } from '../routes'; import { itemEnhancer } from '../enhancers/enhancers'; const Pagination = props => { - const { loading, error } = props.itemsQuery; - - if (loading) return <p>Loading Item...</p>; - + if (props.loading) return <p>Loading Item...</p>; const { aggregate, pageInfo } = props.itemsQuery.itemsConnection; const { page } = props; diff --git a/frontend/components/RemoveFromCart.js b/frontend/components/RemoveFromCart.js index e3eb919..8d0d214 100644 --- a/frontend/components/RemoveFromCart.js +++ b/frontend/components/RemoveFromCart.js @@ -5,6 +5,16 @@ import styled from 'styled-components'; import PropTypes from 'prop-types'; import { removeFromCartEnhancer } from '../enhancers/enhancers'; +const BigButton = styled.button` + font-size: 3rem; + background: none; + border: 0; + &:hover { + color: ${props => props.theme.red}; + cursor: pointer; + } +`; + class RemoveFromCart extends Component { handleRemoveFromCart = async () => { console.log(`gonna remove item from cart`); @@ -18,7 +28,11 @@ class RemoveFromCart extends Component { }; render() { - return <button onClick={this.handleRemoveFromCart}>× Remove</button>; + return ( + <BigButton title="Remove From Cart" onClick={this.handleRemoveFromCart}> + × + </BigButton> + ); } } diff --git a/frontend/components/Search.js b/frontend/components/Search.js index 0ce136e..33017dd 100644 --- a/frontend/components/Search.js +++ b/frontend/components/Search.js @@ -1,9 +1,9 @@ import Downshift from 'downshift'; -import { SEARCH_ITEMS_QUERY } from '../queries'; import { graphql, compose } from 'react-apollo'; -import makeImage from '../lib/image'; -import { Router } from '../routes'; import slugify from 'slugify'; +import styled from 'styled-components'; +import { SEARCH_ITEMS_QUERY } from '../queries'; +import { Router } from '../routes'; function routeToItem(item) { Router.pushRoute('item', { @@ -12,38 +12,62 @@ function routeToItem(item) { }); } -function BasicAutocomplete(props) { +const DropDown = styled.div` + position: absolute; + width: 100%; + z-index: 2; + border: 1px solid ${props => props.theme.lightgrey}; + input { + border: 0; + } +`; + +const DropDownItem = styled.div` + border-bottom: 1px solid ${props => props.theme.lightgrey}; + background: ${props => (props.highlighted ? '#f7f7f7' : 'white')}; + padding: 1rem; + transition: all 0.2s; + ${props => (props.highlighted ? 'padding-left: 2rem;' : null)}; + display: flex; + align-items: center; + border-left: 10px solid ${props => (props.highlighted ? props.theme.lightgrey : 'white')}; + img { + margin-right: 10px; + } +`; + +const SearchStyles = styled.div` + position: relative; +`; + +function AutoComplete(props) { const { items, onChange } = props; return ( - <Downshift onChange={routeToItem} itemToString={item => item.title}> + <Downshift onChange={routeToItem} itemToString={i => (i === null ? '' : i.title)}> {({ getInputProps, getItemProps, isOpen, inputValue, selectedItem, highlightedIndex }) => ( <div> + {/* This is the searchInput */} <input {...getInputProps({ placeholder: 'Search For Item', - onChange: e => props.refetch({ searchTerm: e.target.value }), - style: { fontSize: '20px', padding: '20px', display: 'block', width: '100%' }, + id: 'search', + onChange: e => { + console.log('Change happened'); + props.refetch({ searchTerm: e.target.value }); + }, + style: { fontSize: '20px', padding: '10px', display: 'block', width: '100%' }, })} /> {isOpen ? ( - <div> + <DropDown> + {/* This is the Dropdown */} {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} /> + <DropDownItem {...getItemProps({ item })} key={item.id} highlighted={highlightedIndex === index}> + <img width="50" src={item.image} alt={item.title} /> {item.title} - </div> + </DropDownItem> ))} - </div> + </DropDown> ) : null} </div> )} @@ -52,16 +76,19 @@ function BasicAutocomplete(props) { } const Search = props => ( - <BasicAutocomplete - items={props.searchItems.allItems} - onChange={selectedItem => console.log(selectedItem)} - refetch={props.searchItems.refetch} - /> + <SearchStyles> + <AutoComplete + items={props.searchItems.items} + onChange={selectedItem => console.log(selectedItem)} + refetch={props.searchItems.refetch} + /> + </SearchStyles> ); const searchEnhancer = graphql(SEARCH_ITEMS_QUERY, { name: 'searchItems', options: { + // TODO: Why is this camo variables: { searchTerm: 'camo' }, }, }); diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js index 47dbd0e..58da71c 100644 --- a/frontend/components/SingleItem.js +++ b/frontend/components/SingleItem.js @@ -3,9 +3,9 @@ import { SINGLE_ITEM_QUERY } from '../queries'; import { singleItemEnhancer } from '../enhancers/enhancers'; const SingleItem = props => { - if (props.loading || !props.item) return <p>Loading...</p>; - if (props.error) return <p>Error...</p>; console.log(props); + if (props.loading) return <p>Loading...</p>; + if (props.error) return <p>Error...</p>; const item = props.findItem.items[0]; return ( <div> diff --git a/frontend/components/TakeMyMoney.js b/frontend/components/TakeMyMoney.js index 30a19dc..5bdc409 100644 --- a/frontend/components/TakeMyMoney.js +++ b/frontend/components/TakeMyMoney.js @@ -7,22 +7,12 @@ import formatMoney from '../lib/formatMoney'; class TakeMyMoney extends Component { onToken = async res => { console.log('We got a toooken!'); - console.log(res); const order = await this.props.createOrder({ variables: { token: res.id, }, }); console.log(order); - // const token = res.id; - // const userId = this.props.currentUserQuery.user.id; - // const itemId = this.props.id; - // console.log(`Going to make a purchase with ${token}`); - // console.log(`THe person that bought this was ${userId}`); - // console.log(`The item id is ${itemId}`); - // const charge = await this.props.createOrder({ variables: { token, userId, itemId } }); - // alert(`Back from the charge! ${charge.id}`); - // console.log(charge); }; render() { @@ -31,21 +21,18 @@ class TakeMyMoney extends Component { 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 ( - <div> - <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} - > - {formatMoney(total)} - {this.props.children} - </StripeCheckout> - </div> + <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> ); } } diff --git a/frontend/components/ToggleCart.js b/frontend/components/ToggleCart.js new file mode 100644 index 0000000..c80baa6 --- /dev/null +++ b/frontend/components/ToggleCart.js @@ -0,0 +1,22 @@ +import { Component } from 'react'; +import { compose } from 'react-apollo'; +import { uiEnhancer, updateUIEnhancer } from '../enhancers/enhancers'; +import PropTypes from 'prop-types'; + +class ToggleCart extends Component { + static propTypes = { + render: PropTypes.func.isRequired, + toggleCart: PropTypes.func.isRequired, + ui: PropTypes.object.isRequired, + }; + toggle = e => { + // if this is a keyPress event, make sure it's the enter key + if (e.keyCode && e.keyCode !== 16) return; + this.props.toggleCart(!this.props.ui.uiData.isCartOpen); + }; + render() { + return this.props.render(this.toggle); + } +} + +export default compose(uiEnhancer, updateUIEnhancer)(ToggleCart); diff --git a/frontend/components/styles/Title.js b/frontend/components/styles/Title.js new file mode 100644 index 0000000..e1901ec --- /dev/null +++ b/frontend/components/styles/Title.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +const Title = styled.h3` + margin: 0 1rem; + text-align: center; + transform: skew(-5deg) rotate(-1deg); + margin-top: -3rem; + text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.1); + a { + background: ${props => props.theme.red}; + display: inline; + line-height: 1.3; + font-size: 4rem; + text-align: center; + color: white; + padding: 0 1rem; + } +`; + +export default Title; diff --git a/frontend/enhancers/enhancers.js b/frontend/enhancers/enhancers.js index 88bebe4..ded59b9 100644 --- a/frontend/enhancers/enhancers.js +++ b/frontend/enhancers/enhancers.js @@ -6,8 +6,9 @@ import { SINGLE_ITEM_QUERY, CURRENT_USER_QUERY, ADD_TO_CART_MUTATION, - UPDATE_NETWORK_STATUS, + UPDATE_UI, GET_UI_STATE, + TOGGLE_CART, } from '../queries/index'; export const itemEnhancer = graphql(ALL_ITEMS_QUERY, { @@ -79,8 +80,21 @@ export const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }) export const uiEnhancer = graphql(GET_UI_STATE, { name: 'ui' }); -export const networkEnhancer = graphql(UPDATE_NETWORK_STATUS, { - props: ({ mutate }) => ({ - updateNetworkStatus: isConnected => mutate({ variables: { isConnected } }), +export const updateUIEnhancer = graphql(UPDATE_UI, { + props: data => ({ + updateNetworkStatus: isConnected => + data.mutate({ + variables: { + ...data.ownProps.ui.uiData, + isConnected, + }, + }), + toggleCart: isCartOpen => + data.mutate({ + variables: { + ...data.ownProps.ui.uiData, + isCartOpen, + }, + }), }), }); diff --git a/frontend/lib/image.js b/frontend/lib/image.js deleted file mode 100644 index 5b6efb9..0000000 --- a/frontend/lib/image.js +++ /dev/null @@ -1,6 +0,0 @@ -import { imageEndpoint } from '../config'; - -export default function(image, width = 300, height = 300) { - if (!image || !image.secret) return ''; - return `${imageEndpoint}/${image.secret}/${width}x${height}`; -} diff --git a/frontend/lib/initApollo.js b/frontend/lib/initApollo.js index 45f0fec..e86c7e1 100644 --- a/frontend/lib/initApollo.js +++ b/frontend/lib/initApollo.js @@ -20,20 +20,19 @@ function create(initialState) { const stateLink = withClientState({ cache, defaults: { - networkStatus: { - __typename: 'NetworkStatus', + uiData: { + __typename: 'UIData', isConnected: true, - isCartOpen: true, + isCartOpen: false, }, }, resolvers: { Mutation: { - updateNetworkStatus: (_, { isConnected }, { cache }) => { - console.log(isConnected); + setLocalData: (_, values, { cache }) => { const data = { - networkStatus: { - __typename: 'NetworkStatus', - isConnected, + uiData: { + __typename: 'UIData', + ...values, }, }; cache.writeData({ data }); diff --git a/frontend/pages/_document.js b/frontend/pages/_document.js new file mode 100644 index 0000000..e9a6570 --- /dev/null +++ b/frontend/pages/_document.js @@ -0,0 +1,26 @@ +import Document, { Head, Main, NextScript } from 'next/document'; +import { ServerStyleSheet } from 'styled-components'; + +export default class MyDocument extends Document { + static getInitialProps({ renderPage }) { + const sheet = new ServerStyleSheet(); + const page = renderPage(App => props => sheet.collectStyles(<App {...props} />)); + const styleTags = sheet.getStyleElement(); + return { ...page, styleTags }; + } + + render() { + return ( + <html> + <Head> + <title>My page</title> + {this.props.styleTags} + </Head> + <body> + <Main /> + <NextScript /> + </body> + </html> + ); + } +} diff --git a/frontend/pages/cart.js b/frontend/pages/cart.js deleted file mode 100644 index afdacf7..0000000 --- a/frontend/pages/cart.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Component } from 'react'; -import withData from '../lib/withData'; -import Items from '../components/Items'; -import CreateItem from '../components/CreateItem'; -import Signup from '../components/Signup'; -import LoginAuth0 from '../components/LoginAuth0'; -import Page from '../components/Page'; -import CartList from '../components/CartList'; - -class Orders extends Component { - render() { - return ( - <Page> - <h2>My Cart</h2> - <CartList /> - </Page> - ); - } -} - -export default withData(Orders); diff --git a/frontend/pages/index.js b/frontend/pages/index.js index 128ccf1..d4625c5 100644 --- a/frontend/pages/index.js +++ b/frontend/pages/index.js @@ -1,22 +1,19 @@ -import { Component } from "react"; -import PropTypes from "prop-types"; -import Items from "../components/Items"; -import Header from "../components/Header"; -import Page from "../components/Page"; -import withData from "../lib/withData"; +import PropTypes from 'prop-types'; +import Items from '../components/Items'; +import Page from '../components/Page'; +import withData from '../lib/withData'; const Home = props => { const page = parseInt(props.url.query.page) || 1; return ( <Page> - <p>Hello</p> <Items page={page} /> </Page> ); }; Home.propTypes = { - url: PropTypes.object + url: PropTypes.object.isRequired, }; export default withData(Home); diff --git a/frontend/pages/orders.js b/frontend/pages/orders.js index 4025868..15004a9 100644 --- a/frontend/pages/orders.js +++ b/frontend/pages/orders.js @@ -1,9 +1,5 @@ import { Component } from 'react'; import withData from '../lib/withData'; -import Items from '../components/Items'; -import CreateItem from '../components/CreateItem'; -import Signup from '../components/Signup'; -import LoginAuth0 from '../components/LoginAuth0'; import Page from '../components/Page'; import OrderList from '../components/OrderList'; @@ -12,9 +8,9 @@ class Orders extends Component { return ( <Page> <h2>My Orders</h2> - <OrderList/> + <OrderList /> </Page> - ) + ); } } diff --git a/frontend/queries/index.js b/frontend/queries/index.js index 2464246..beaec05 100644 --- a/frontend/queries/index.js +++ b/frontend/queries/index.js @@ -102,8 +102,8 @@ export const SINGLE_ITEM_QUERY = gql` export const SEARCH_ITEMS_QUERY = gql` ${itemDetails} query SearchItems($searchTerm: String!) { - allItems( - filter: { + items( + where: { OR: [ { title_contains: $searchTerm } { description_contains: $searchTerm } @@ -238,32 +238,17 @@ export const GET_CART_STATE = gql` } `; -export const UPDATE_CART_STATE = gql` - mutation updateCartState($isCartOpen: Boolean!) { - updateCartState(isCartOpen: $isCartOpen) { - ui { - isCartOpen - } - } - } -`; - -export const UI_STATE = gql` - query { - isCartOpen @client - } -`; - -export const UPDATE_NETWORK_STATUS = gql` - mutation updateNetworkStatus($isConnected: Boolean) { - updateNetworkStatus(isConnected: $isConnected) @client +export const UPDATE_UI = gql` + mutation setLocalData($isConnected: Boolean, $isCartOpen: Boolean) { + setLocalData(isConnected: $isConnected, isCartOpen: $isCartOpen) @client } `; export const GET_UI_STATE = gql` query cartState { - networkStatus @client { + uiData @client { isConnected + isCartOpen } } `; diff --git a/frontend/static/favicon.png b/frontend/static/favicon.png Binary files differnew file mode 100644 index 0000000..0601ad6 --- /dev/null +++ b/frontend/static/favicon.png diff --git a/frontend/static/nprogress.css b/frontend/static/nprogress.css index e18709f..544ce01 100644 --- a/frontend/static/nprogress.css +++ b/frontend/static/nprogress.css @@ -4,7 +4,7 @@ } #nprogress .bar { - background: #29d; + background: red; position: fixed; z-index: 1031; top: 0; @@ -21,7 +21,7 @@ right: 0px; width: 100px; height: 100%; - box-shadow: 0 0 10px #29d, 0 0 5px #29d; + box-shadow: 0 0 10px red, 0 0 5px red; opacity: 1.0; -webkit-transform: rotate(3deg) translate(0px, -4px); @@ -44,8 +44,8 @@ box-sizing: border-box; border: solid 2px transparent; - border-top-color: #29d; - border-left-color: #29d; + border-top-color: red; + border-left-color: red; border-radius: 50%; -webkit-animation: nprogress-spinner 400ms linear infinite; |
