diff options
Diffstat (limited to 'finished-application/frontend/components')
46 files changed, 0 insertions, 2731 deletions
diff --git a/finished-application/frontend/components/AddToCart.js b/finished-application/frontend/components/AddToCart.js deleted file mode 100644 index 03242a4..0000000 --- a/finished-application/frontend/components/AddToCart.js +++ /dev/null @@ -1,68 +0,0 @@ -import { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import User, { CURRENT_USER_QUERY } from './User'; - -const ADD_TO_CART_MUTATION = gql` - mutation addToCart($id: ID!) { - addToCart(id: $id) { - id - quantity - item { - id - price - description - image - title - } - } - } -`; - -class AddToCart extends Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - - update = (cache, payload) => { - const newCartItem = payload.data.addToCart; - const data = cache.readQuery({ query: CURRENT_USER_QUERY }); - - const existingIndex = data.me.cart.findIndex(cartItem => cartItem.id === newCartItem.id); - if (existingIndex >= 0) { - // already in cache, just replace it - data.me.cart = [ - ...data.me.cart.slice(0, existingIndex), - newCartItem, - ...data.me.cart.slice(existingIndex + 1), - ]; - } else { - data.me.cart = [...data.me.cart, newCartItem]; - } - cache.writeQuery({ query: CURRENT_USER_QUERY, data }); - }; - - render() { - const { id } = this.props; - return ( - <User> - {({ data: { me }, loading }) => { - if (!me || loading) return null; - return ( - <Mutation mutation={ADD_TO_CART_MUTATION} variables={{ id }} update={this.update}> - {(addToCart, { loading }) => ( - <button disabled={loading} onClick={addToCart}> - 🛒 Add{loading && 'ing'} To Cart - </button> - )} - </Mutation> - ); - }} - </User> - ); - } -} - -export default AddToCart; -export { ADD_TO_CART_MUTATION }; diff --git a/finished-application/frontend/components/Cart.js b/finished-application/frontend/components/Cart.js deleted file mode 100644 index 7e8a9c5..0000000 --- a/finished-application/frontend/components/Cart.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import { Mutation, Query } from 'react-apollo'; -import { adopt } from 'react-adopt'; -import gql from 'graphql-tag'; -import TakeMyMoney from './TakeMyMoney'; -import formatMoney from '../lib/formatMoney'; -import CartItem from './CartItem'; -import { CURRENT_USER_QUERY } from './User'; -import calcTotalPrice from '../lib/calcTotalPrice'; -import Error from './ErrorMessage'; -import CartStyles from './styles/CartStyles'; -import Supreme from './styles/Supreme'; -import CloseButton from './styles/CloseButton'; -import SickButton from './styles/SickButton'; - -const LOCAL_STATE_QUERY = gql` - query { - cartOpen @client - } -`; - -const TOGGLE_CART_MUTATION = gql` - mutation { - toggleCart @client - } -`; - -const Composed = adopt({ - toggleCart: ({ render }) => ( - <Mutation mutation={TOGGLE_CART_MUTATION}> - {(mutate, result) => render({ mutate, result })} - </Mutation> - ), - localState: ({ render }) => <Query query={LOCAL_STATE_QUERY} children={render} />, - currentUser: ({ render }) => ( - <Query children={render} query={CURRENT_USER_QUERY} data-test="cart" /> - ), -}); - -const Cart = () => ( - <Composed> - {({ toggleCart, localState, currentUser }) => { - const { data: { me }, error, loading } = currentUser; - if (loading) return <p>Loading...</p>; - if (error) return <Error error={error} />; - if (!me) return null; - return ( - <CartStyles open={localState.data.cartOpen}> - <header> - <CloseButton title="close" onClick={toggleCart.mutate}> - × - </CloseButton> - - <Supreme>{me.name}'s Cart.</Supreme> - <p> - You have {me.cart.length} item{me.cart.length === 1 ? '' : 's'} in your cart. - </p> - </header> - - <ul>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</ul> - <footer> - <p>{formatMoney(calcTotalPrice(me.cart))}</p> - <TakeMyMoney> - <SickButton>Checkout</SickButton> - </TakeMyMoney> - </footer> - </CartStyles> - ); - }} - </Composed> -); - -export default Cart; -export { LOCAL_STATE_QUERY, TOGGLE_CART_MUTATION }; diff --git a/finished-application/frontend/components/CartCount.js b/finished-application/frontend/components/CartCount.js deleted file mode 100644 index ae51f20..0000000 --- a/finished-application/frontend/components/CartCount.js +++ /dev/null @@ -1,64 +0,0 @@ -import styled from 'styled-components'; -import { TransitionGroup, CSSTransition } from 'react-transition-group'; -import PropTypes from 'prop-types'; - -const Dot = styled.div` - background: ${props => props.theme.red}; - color: white; - border-radius: 50%; - padding: 0.5rem; - line-height: 2rem; - min-width: 3rem; - margin-left: 1rem; - font-weight: 100; - font-feature-settings: 'tnum'; - font-variant-numeric: tabular-nums; -`; - -const AnimationStyles = styled.span` - position: relative; - .count { - display: block; - position: relative; - transition: all 0.4s; - backface-visibility: hidden; - } - .count-enter { - transform: rotateX(0.5turn); - } - - .count-enter-active { - transform: rotateX(0); - } - - .count-exit { - top: 0; - position: absolute; - transform: rotateX(0); - } - - .count-exit-active { - transform: rotateX(0.5turn); - } -`; - -const CartCount = ({ count }) => ( - <AnimationStyles> - <TransitionGroup> - <CSSTransition - unmountOnExit - className="count" - classNames="count" - key={count} - timeout={{ enter: 400, exit: 400 }} - > - <Dot>{count}</Dot> - </CSSTransition> - </TransitionGroup> - </AnimationStyles> -); - -CartCount.propTypes = { - count: PropTypes.number.isRequired, -}; -export default CartCount; diff --git a/finished-application/frontend/components/CartItem.js b/finished-application/frontend/components/CartItem.js deleted file mode 100644 index 6c6d575..0000000 --- a/finished-application/frontend/components/CartItem.js +++ /dev/null @@ -1,53 +0,0 @@ -import styled from 'styled-components'; -import PropTypes from 'prop-types'; -import formatMoney from '../lib/formatMoney'; -import RemoveFromCart from './RemoveFromCart'; - -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 }) => { - if (!cartItem.item) - return ( - <CartItemStyles key={cartItem.id}> - <p>Ack! That Item is gone!</p> - <RemoveFromCart id={cartItem.id} /> - </CartItemStyles> - ); - return ( - <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> - ); -}; - -CartItem.propTypes = { - cartItem: PropTypes.object.isRequired, -}; - -export default CartItem; diff --git a/finished-application/frontend/components/CreateItem.js b/finished-application/frontend/components/CreateItem.js deleted file mode 100644 index 503b3cb..0000000 --- a/finished-application/frontend/components/CreateItem.js +++ /dev/null @@ -1,148 +0,0 @@ -import React, { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import Router from 'next/router'; -import wait from 'waait'; -import gql from 'graphql-tag'; -import Error from './ErrorMessage'; -import Form from './styles/Form'; -import formatMoney from '../lib/formatMoney'; -import { ALL_ITEMS_QUERY } from './Items'; -import { PAGINATION_QUERY } from './Pagination'; - -const CREATE_ITEM_MUTATION = gql` - mutation CREATE_ITEM_MUTATION( - $description: String! - $title: String! - $price: Int! - $image: String - $largeImage: String - ) { - createItem( - description: $description - title: $title - price: $price - image: $image - largeImage: $largeImage - ) { - id - } - } -`; - -class CreateItem extends Component { - state = { - title: '', - description: '', - image: '', - largeImage: '', - price: 0, - }; - - handleChange = e => { - const { name, value, type } = e.target; - const val = type === 'number' ? parseFloat(value) : value; - this.setState({ [name]: val }); - }; - - uploadFile = async e => { - this.setState({ loading: true }); - const files = e.currentTarget.files; - const data = new FormData(); - data.append('file', files[0]); - data.append('upload_preset', 'sickfits'); - - // use the file endpoint - const res = await fetch('https://api.cloudinary.com/v1_1/wesbos/image/upload', { - method: 'POST', - body: data, - }); - const file = await res.json(); - this.setState({ - image: file.secure_url, - largeImage: file.eager[0].secure_url, - loading: false, - }); - }; - - render() { - return ( - <Mutation - mutation={CREATE_ITEM_MUTATION} - variables={this.state} - refetchQueries={[{ query: ALL_ITEMS_QUERY }, { query: PAGINATION_QUERY }]} - > - {(createItem, { loading, error }) => ( - <Form - data-test - onSubmit={async e => { - e.preventDefault(); - const { data: { createItem: item } } = await createItem(); - // we wait 0 ms so it puts the router push at the end of the call stack. This ensures that refetchQueries runs before we unmount the component :) - await wait(); - Router.push({ - pathname: `/item`, - query: { id: item.id }, - }); - }} - > - <h2>Sell an Item.</h2> - <Error error={error} /> - <fieldset disabled={loading} aria-busy={loading}> - <label htmlFor="file"> - Image - <input - required - id="file" - onChange={this.uploadFile} - type="file" - accept=".png, .jpg, .jpeg" - /> - {this.state.image ? ( - <img src={this.state.image} width="100" alt={this.state.title} /> - ) : null} - </label> - <label htmlFor="title"> - Title - <input - required - value={this.state.title} - onChange={this.handleChange} - type="text" - name="title" - id="title" - placeholder="Title" - /> - </label> - <label htmlFor="price"> - Price {this.state.price && formatMoney(this.state.price)} - <input - required - type="number" - id="price" - name="price" - min="0" - value={this.state.price} - onChange={this.handleChange} - /> - </label> - <textarea - id="description" - required - name="description" - value={this.state.description} - onChange={this.handleChange} - placeholder="The desc for this item" - /> - <button disabled={this.state.loading} type="submit"> - Submit - </button> - </fieldset> - </Form> - )} - </Mutation> - ); - } -} - -export default CreateItem; -export { CREATE_ITEM_MUTATION }; diff --git a/finished-application/frontend/components/DeleteItem.js b/finished-application/frontend/components/DeleteItem.js deleted file mode 100644 index 4d68d5e..0000000 --- a/finished-application/frontend/components/DeleteItem.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { Mutation } from 'react-apollo'; -import PropTypes, { number } from 'prop-types'; -import gql from 'graphql-tag'; -import { withRouter } from 'next/router'; -import { ALL_ITEMS_QUERY } from './Items'; -import { PAGINATION_QUERY } from './Pagination'; -import { perPage } from '../config'; - -const DELETE_ITEM_MUTATION = gql` - mutation deleteItem($id: ID!) { - deleteItem(id: $id) { - id - title - description - } - } -`; - -class DeleteItem extends React.Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - - update = (cache, payload) => { - const deletedItem = payload.data.deleteItem; - let { page = 1 } = this.props.router.query; - page = parseFloat(page); - const skip = page * perPage - perPage; - const variables = { skip }; - const data = cache.readQuery({ query: ALL_ITEMS_QUERY, variables }); - // filter this one out - data.items = data.items.filter(item => item.id !== deletedItem.id); - // write the data back to the cache - console.log(data.items); - cache.writeQuery({ query: ALL_ITEMS_QUERY, data, variables }); - // FYI Pagination is broken with Apollo currently - will make a followup video - }; - - render() { - return ( - <Mutation - mutation={DELETE_ITEM_MUTATION} - variables={{ id: this.props.id }} - refetchQueries={[ - { - query: ALL_ITEMS_QUERY, - variables: { skip: (this.props.router.query.page || 1) * perPage - perPage }, - }, - { query: PAGINATION_QUERY }, - ]} - update={this.update} - > - {(removeItem, { error }) => ( - <button - onClick={() => { - if (confirm('Are you sure you want to delete this item?')) { - removeItem(); - } - }} - > - {error ? error.message : '× Delete Item'} - </button> - )} - </Mutation> - ); - } -} - -export default withRouter(DeleteItem); -export { DELETE_ITEM_MUTATION }; diff --git a/finished-application/frontend/components/Dump.js b/finished-application/frontend/components/Dump.js deleted file mode 100644 index f281281..0000000 --- a/finished-application/frontend/components/Dump.js +++ /dev/null @@ -1,19 +0,0 @@ -const Dump = props => ( - <div - style={{ - fontSize: 20, - border: '1px solid #efefef', - padding: 10, - background: 'white', - }} - > - {Object.keys(props).map(prop => ( - <pre key={prop}> - <strong style={{ color: 'white', background: 'red' }}>{prop} 💩</strong> - {JSON.stringify(props[prop], '', ' ')} - </pre> - ))} - </div> -); - -export default Dump; diff --git a/finished-application/frontend/components/EditUser.js b/finished-application/frontend/components/EditUser.js deleted file mode 100644 index 1bb8e5b..0000000 --- a/finished-application/frontend/components/EditUser.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { Query, Mutation } from 'react-apollo'; -import gql from 'graphql-tag'; -import Form from './styles/Form'; -import { CURRENT_USER_QUERY } from './User'; -import Error from './ErrorMessage'; -import User from './User'; - -const UPDATE_USER_MUTATION = gql` - mutation updateUser($name: String!) { - updateUser(name: $name) { - name - } - } -`; - -class EditUser extends React.Component { - state = { - changes: {}, - }; - - handleChange = e => { - const { name, value } = e.target; - const changes = { - ...this.state.changes, - [name]: value, - }; - this.setState({ changes }); - }; - - handleSubmit = async (e, updateUser) => { - e.preventDefault(); - // only submit if there are real changes - if (Object.keys(this.state.changes).length === 0) return; - await updateUser(); - this.setState({ changes: {} }); - }; - - render() { - return ( - <User> - {({ data: { me }, loading }) => { - if (loading) return <p>Loading...</p>; - return ( - <Mutation - mutation={UPDATE_USER_MUTATION} - refetchQueries={[{ query: CURRENT_USER_QUERY }]} - variables={this.state.changes} - > - {(updateUser, { error, called }) => ( - <Form onSubmit={e => this.handleSubmit(e, updateUser)}> - <Error error={error} /> - <fieldset disabled={loading} aria-busy={loading}> - {called && !error && <p data-test="updated">Updated!</p>} - <label htmlFor="name"> - Name: - <input - type="text" - name="name" - defaultValue={me.name} - onChange={this.handleChange} - /> - </label> - <button type="submit">Update</button> - <strong>me:</strong> - <pre>{JSON.stringify(me.name)}</pre> - <strong>Change:</strong> - <pre data-test="change">{JSON.stringify(this.state.changes)}</pre> - </fieldset> - </Form> - )} - </Mutation> - ); - }} - </User> - ); - } -} - -export default EditUser; -export { UPDATE_USER_MUTATION }; diff --git a/finished-application/frontend/components/ErrorMessage.js b/finished-application/frontend/components/ErrorMessage.js deleted file mode 100644 index 52d59a8..0000000 --- a/finished-application/frontend/components/ErrorMessage.js +++ /dev/null @@ -1,51 +0,0 @@ -import styled from 'styled-components'; -import React from 'react'; - -import PropTypes from 'prop-types'; - -const ErrorStyles = styled.div` - padding: 2rem; - background: white; - margin: 2rem 0; - border: 1px solid rgba(0, 0, 0, 0.05); - border-left: 5px solid red; - p { - margin: 0; - font-weight: 100; - } - strong { - margin-right: 1rem; - } -`; - -const DisplayError = ({ error }) => { - if (!error || !error.message) return null; - if (error.networkError && error.networkError.result && error.networkError.result.errors.length) { - return error.networkError.result.errors.map((error, i) => ( - <ErrorStyles key={i}> - <p data-test="graphql-error"> - <strong>Shoot!</strong> - {error.message.replace('GraphQL error: ', '')} - </p> - </ErrorStyles> - )); - } - return ( - <ErrorStyles> - <p data-test="graphql-error"> - <strong>Shoot!</strong> - {error.message.replace('GraphQL error: ', '')} - </p> - </ErrorStyles> - ); -}; - -DisplayError.defaultProps = { - error: {}, -}; - -DisplayError.propTypes = { - error: PropTypes.object, -}; - -export default DisplayError; diff --git a/finished-application/frontend/components/Header.js b/finished-application/frontend/components/Header.js deleted file mode 100644 index bb05c34..0000000 --- a/finished-application/frontend/components/Header.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import NProgress from 'nprogress'; -import Router from 'next/router'; -import styled from 'styled-components'; -import Link from 'next/link'; -import Cart from './Cart'; -import Search from './Search'; -import Nav from './Nav'; - -Router.onRouteChangeStart = () => { - NProgress.start(); -}; -Router.onRouteChangeComplete = () => NProgress.done(); -Router.onRouteChangeError = () => NProgress.done(); - -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; - @media (max-width: 1300px) { - grid-template-columns: 1fr; - justify-content: center; - } - } - .sub-bar { - display: grid; - grid-template-columns: 1fr auto; - border-bottom: 1px solid ${props => props.theme.lightgrey}; - } -`; - -const Logo = styled.h1` - font-size: 4rem; - margin-left: 2rem; - position: relative; - z-index: 2; - transform: skew(-7deg); - a { - padding: 0.5rem 1rem; - background: ${props => props.theme.red}; - color: white; - letter-spacing: -2px; - text-transform: uppercase; - } - @media (max-width: 1300px) { - margin: 0; - text-align: center; - } -`; - -const Header = () => ( - <StyledHeader> - <div className="bar"> - <Logo> - <Link href="/"> - <a>Sick Fits!</a> - </Link> - </Logo> - <Nav /> - </div> - <div className="sub-bar"> - <Search /> - </div> - <Cart /> - </StyledHeader> -); - -export default Header; diff --git a/finished-application/frontend/components/Item.js b/finished-application/frontend/components/Item.js deleted file mode 100644 index d6b339e..0000000 --- a/finished-application/frontend/components/Item.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Link from 'next/link'; -import Title from './styles/Title'; -import AddToCart from './AddToCart'; -import DeleteItem from './DeleteItem'; -import formatMoney from '../lib/formatMoney'; -import ItemStyles from './styles/ItemStyles'; -import PriceTag from './styles/PriceTag'; - -class Item extends React.Component { - static propTypes = { - item: PropTypes.object.isRequired, - }; - - render() { - const item = this.props.item; - return ( - <ItemStyles key={item.id}> - {item.image && <img src={item.image} alt={item.title} />} - <Title> - <Link - href={{ - pathname: '/item', - query: { id: item.id }, - }} - > - <a>{item.title}</a> - </Link> - </Title> - - <PriceTag>{formatMoney(item.price)}</PriceTag> - - <p>{item.description}</p> - - <div className="buttonList"> - <Link - href={{ - pathname: '/update', - query: { id: item.id }, - }} - > - <a>Edit ✏️</a> - </Link> - <AddToCart id={item.id} /> - <DeleteItem id={item.id} /> - </div> - </ItemStyles> - ); - } -} - -export default Item; diff --git a/finished-application/frontend/components/Items.js b/finished-application/frontend/components/Items.js deleted file mode 100644 index 5694d0b..0000000 --- a/finished-application/frontend/components/Items.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import { Query } from 'react-apollo'; -import styled from 'styled-components'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import Pagination from './Pagination'; -import Item from './Item'; -import LoadingItem from './LoadingItem'; -import { perPage } from '../config'; - -const ALL_ITEMS_QUERY = gql` - query ALL_ITEMS_QUERY($skip: Int = 0, $first: Int = ${perPage}) { - items(orderBy: createdAt_DESC, first: $first, skip: $skip) { - __typename - id - title - price - description - image - largeImage - } - } -`; - -const Items = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 60px; - max-width: ${props => props.theme.maxWidth}; - margin: 0 auto; -`; - -const Center = styled.div` - text-align: center; -`; - -class ItemList extends React.Component { - static propTypes = { - page: PropTypes.number.isRequired, - }; - render() { - return ( - <Center key={this.props.page}> - <Pagination page={this.props.page} /> - <Query - query={ALL_ITEMS_QUERY} - variables={{ - skip: this.props.page * perPage - perPage, - first: perPage, - }} - // fetchPolicy="network-only" - > - {({ data, error, loading }) => { - if (loading) { - return ( - <Items> - {Array.from({ length: 4 }) - .map((x, id) => ({ id })) - .map(x => <LoadingItem key={x.id} />)} - </Items> - ); - } - if (error) return <div>Error</div>; - return <Items>{data.items.map(item => <Item key={item.id} item={item} />)}</Items>; - }} - </Query> - <Pagination page={this.props.page} /> - </Center> - ); - } -} - -export default ItemList; -export { ALL_ITEMS_QUERY }; diff --git a/finished-application/frontend/components/LoadingItem.js b/finished-application/frontend/components/LoadingItem.js deleted file mode 100644 index 5736337..0000000 --- a/finished-application/frontend/components/LoadingItem.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import Title from './styles/Title'; -import ItemStyles from './styles/ItemStyles'; - -class LoadingItem extends React.Component { - render() { - return ( - <ItemStyles> - <img - src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" - alt="Loading..." - />> - <Title> - <a>Loading...</a> - </Title> - <p>Please Wait</p> - </ItemStyles> - ); - } -} - -export default LoadingItem; diff --git a/finished-application/frontend/components/Meta.js b/finished-application/frontend/components/Meta.js deleted file mode 100644 index fe9c29f..0000000 --- a/finished-application/frontend/components/Meta.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import Head from 'next/head'; - -const Meta = () => ( - <div> - <Head> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta charSet="utf-8" /> - <link rel="shortcut icon" href="/static/favicon.png" /> - <link rel="stylesheet" type="text/css" href="/static/nprogress.css" /> - <title>Sick Fits!</title> - </Head> - </div> -); - -export default Meta; diff --git a/finished-application/frontend/components/Nav.js b/finished-application/frontend/components/Nav.js deleted file mode 100644 index 5f3495a..0000000 --- a/finished-application/frontend/components/Nav.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, { Fragment } from 'react'; -import Link from 'next/link'; -import { Query, Mutation } from 'react-apollo'; -import { TOGGLE_CART_MUTATION } from './Cart'; -import User from './User'; -import CartCount from './CartCount'; -import Signout from './Signout'; -import NavStyles from './styles/NavStyles'; - -class Nav extends React.Component { - render() { - // below we set the fetchPolicy to network only so it forces re-fetch on the server - return ( - <User> - {({ data: { me } }) => ( - <NavStyles data-test="nav"> - <Link href="/items"> - <a>Shop</a> - </Link> - <Link href="/sell"> - <a>Sell</a> - </Link> - - {!me && ( - <Link href="/signup"> - <a>Sign In</a> - </Link> - )} - - {me && ( - <Fragment> - <Link href="/orders"> - <a>Orders</a> - </Link> - <Link href="/me"> - <a>My Account</a> - </Link> - <Signout /> - <Mutation mutation={TOGGLE_CART_MUTATION}> - {toggleCart => ( - <button onClick={toggleCart}> - My Cart - <CartCount - className="cart-count" - count={me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)} - /> - </button> - )} - </Mutation> - </Fragment> - )} - </NavStyles> - )} - </User> - ); - } -} - -export default Nav; diff --git a/finished-application/frontend/components/Order.js b/finished-application/frontend/components/Order.js deleted file mode 100644 index b4a0a0e..0000000 --- a/finished-application/frontend/components/Order.js +++ /dev/null @@ -1,98 +0,0 @@ -import { Component } from 'react'; -import { Query } from 'react-apollo'; -import { format } from 'date-fns'; -import Head from 'next/head'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import formatMoney from '../lib/formatMoney'; -import Error from './ErrorMessage'; -import OrderStyles from './styles/OrderStyles'; - -const SINGLE_ORDER_QUERY = gql` - query SINGLE_ORDER_QUERY($id: ID!) { - order(id: $id) { - id - charge - total - createdAt - user { - id - } - items { - id - title - price - description - image - quantity - } - } - } -`; - -class Order extends Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - - render() { - return ( - <Query - query={SINGLE_ORDER_QUERY} - variables={{ id: this.props.id }} - fetchPolicy="network-only" - > - {({ data, error, loading, refetch }) => { - if (loading) return <p>Loading...</p>; - if (error) return <Error error={error} refetch={refetch} />; - const order = data.order; - return ( - <OrderStyles data-test="order"> - <Head> - <title>Sick Fits - Order {order.id}</title> - </Head> - <p> - <span>Order Id:</span> - <span>{order.id}</span> - </p> - <p> - <span>Charge</span> - <span>{order.charge}</span> - </p> - <p> - <span>Date</span> - <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span> - </p> - <p> - <span>Order Total</span> - <span>{formatMoney(order.total)}</span> - </p> - <p> - <span>Item Count</span> - <span>{order.items.length}</span> - </p> - <div className="items"> - {order.items.map(item => ( - <div className="order-item" key={item.id}> - <img src={item.image} alt={item.title} /> - <div className="item-details"> - <h2> {item.title} </h2> - <p>Qty: {item.quantity}</p> - <p>Each: {formatMoney(item.price)}</p> - <p>Subtotal: {formatMoney(item.price * item.quantity)}</p> - <p>SubTotal: {item.description}</p> - <p>{item.description}</p> - </div> - </div> - ))} - </div> - </OrderStyles> - ); - }} - </Query> - ); - } -} - -export default Order; -export { SINGLE_ORDER_QUERY }; diff --git a/finished-application/frontend/components/OrderList.js b/finished-application/frontend/components/OrderList.js deleted file mode 100644 index 5886ad7..0000000 --- a/finished-application/frontend/components/OrderList.js +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import { Query } from 'react-apollo'; -import { formatDistance } from 'date-fns'; -import Link from 'next/link'; -import styled from 'styled-components'; -import gql from 'graphql-tag'; -import formatMoney from '../lib/formatMoney'; -import OrderItemStyles from './styles/OrderItemStyles'; - -const USER_ORDERS_QUERY = gql` - query orders { - orders(orderBy: createdAt_DESC) { - id - total - createdAt - updatedAt - items { - id - title - price - description - quantity - image - } - } - } -`; - -const OrderUl = styled.ul` - display: grid; - grid-gap: 4rem; - grid-template-columns: repeat(auto-fit, minmax(40%, 1fr)); -`; - -class OrderList extends React.Component { - render() { - return ( - <Query query={USER_ORDERS_QUERY}> - {({ data: { orders }, loading, error }) => { - if (loading) return <p>Loading...</p>; - if (error) return <p>Error...</p>; - if (!orders || !orders.length) return <p>No Orders Yet!</p>; - return ( - <div> - <h2>You have {orders.length} Orders</h2> - <OrderUl> - {orders.map(order => ( - <OrderItemStyles key={order.id}> - <Link - href={{ - pathname: '/order', - query: { id: order.id }, - }} - > - <a> - <div className="order-meta"> - <p> - <strong>{order.items.reduce((a, b) => a + b.quantity, 0)}</strong> - Items - </p> - <p> - <strong>{order.items.length}</strong> - Products - </p> - <p> - <strong>{formatDistance(order.createdAt, new Date())}</strong> - ago - </p> - <p> - <strong>{formatMoney(order.total)}</strong> - Total - </p> - </div> - <div className="images"> - {order.items.map(item => ( - <img key={item.id} src={item.image} alt={item.title} /> - ))} - </div> - </a> - </Link> - </OrderItemStyles> - ))} - </OrderUl> - </div> - ); - }} - </Query> - ); - } -} - -export default OrderList; -export { USER_ORDERS_QUERY }; diff --git a/finished-application/frontend/components/Page.js b/finished-application/frontend/components/Page.js deleted file mode 100644 index c3e04ac..0000000 --- a/finished-application/frontend/components/Page.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import styled, { ThemeProvider, injectGlobal } from 'styled-components'; -import PropTypes from 'prop-types'; -import Header from './Header'; -import Meta from './Meta'; - -const theme = { - red: '#FF0000', - black: '#393939', - grey: '#3A3A3A', - lightgrey: '#E1E1E1', - offWhite: '#EDEDED', - maxWidth: '1300px', - bs: '0 12px 24px 0 rgba(0, 0, 0, 0.09)', -}; - -injectGlobal` - html { - box-sizing: border-box; - font-size: 10px; - } - body { - font-family: 'radnika next', sans-serif; - padding: 0; - background-color: #ffffff; - margin: 0; - font-size: 1.5rem; - line-height: 2; - } - *, *:before, *:after { - box-sizing: inherit; - } - a { - color: ${theme.black}; - text-decoration: none; - } -`; - -const Inner = styled.div` - max-width: 1000px; - margin: 0 auto; - padding: 2rem; -`; - -const StyledPage = styled.div` - color: ${props => props.theme.black}; - background: white; -`; - -class Page extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - }; - render() { - return ( - <ThemeProvider theme={theme}> - <StyledPage> - <Meta /> - <Header /> - <Inner>{this.props.children}</Inner> - </StyledPage> - </ThemeProvider> - ); - } -} - -export default Page; diff --git a/finished-application/frontend/components/Pagination.js b/finished-application/frontend/components/Pagination.js deleted file mode 100644 index f684840..0000000 --- a/finished-application/frontend/components/Pagination.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { Query } from 'react-apollo'; -import styled from 'styled-components'; -import Link from 'next/link'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import Head from 'next/head'; -import { perPage } from '../config'; -import PaginationStyles from './styles/PaginationStyles'; - - - -const PAGINATION_QUERY = gql` - query itemsConnection($skip: Int = 0, $first: Int = 4) { - itemsConnection(orderBy: createdAt_DESC, first: $first, skip: $skip) { - aggregate { - count - } - } - } -`; - -const Pagination = props => ( - <Query query={PAGINATION_QUERY}> - {({ data, loading, error }) => { - if (loading || error) return null; - const { aggregate } = data.itemsConnection; - const { page } = props; - const pages = Math.ceil(aggregate.count / perPage); - return ( - <PaginationStyles data-test="pagination"> - <Head> - <title> - Sick Fits! — Page {page} of {pages} - </title> - </Head> - <Link - prefetch - href={{ - pathname: 'items', - query: { page: page - 1 }, - }} - > - <a className="prev" aria-disabled={page <= 1}> - ←Prev - </a> - </Link> - <p> - Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong> - </p> - <p> - <strong>{aggregate.count}</strong> Items Total - </p> - <Link - prefetch - href={{ - pathname: 'items', - query: { page: page + 1 }, - }} - > - <a className="next" aria-disabled={page >= pages}> - Next → - </a> - </Link> - </PaginationStyles> - ); - }} - </Query> -); - -Pagination.propTypes = { - page: PropTypes.number.isRequired, -}; - -export default Pagination; -export { PAGINATION_QUERY }; diff --git a/finished-application/frontend/components/Permissions.js b/finished-application/frontend/components/Permissions.js deleted file mode 100644 index 52a6acf..0000000 --- a/finished-application/frontend/components/Permissions.js +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { Query, Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import Error from './ErrorMessage'; -import SickButton from './styles/SickButton'; -import Table from './styles/Table'; - -const ALL_USERS_QUERY = gql` - query { - users { - id - name - email - permissions - } - } -`; - -const UPDATE_PERMISSIONS_MUTATION = gql` - mutation updatePermissions($permissions: [Permission], $userId: ID!) { - updatePermissions(permissions: $permissions, userId: $userId) { - id - permissions - name - email - } - } -`; - -const possiblePermissions = [ - 'ADMIN', - 'USER', - 'ITEMCREATE', - 'ITEMUPDATE', - 'ITEMDELETE', - 'PERMISSIONUPDATE', -]; - -class User extends React.Component { - static propTypes = { - user: PropTypes.shape({ - permissions: PropTypes.array.isRequired, - id: PropTypes.string.isRequired, - }).isRequired, - }; - state = { - permissions: this.props.user.permissions, - }; - handlePermissionsChange = e => { - const checkbox = e.target; - let updatedPermissions = [...this.state.permissions]; - if (checkbox.checked) { - // add it in - updatedPermissions.push(checkbox.value); - } else { - updatedPermissions = updatedPermissions.filter(permission => permission !== checkbox.value); - } - this.setState({ permissions: updatedPermissions }); - }; - render() { - const { user } = this.props; - return ( - <Mutation mutation={UPDATE_PERMISSIONS_MUTATION}> - {(updatePermissions, { loading, error }) => ( - <tr key={user.id} className="user"> - <Error error={error} /> - <td>{user.name}</td> - <td>{user.email}</td> - {possiblePermissions.map(permission => ( - <td> - <label key={permission} htmlFor={`${user.id}-permission-${permission}`}> - <input - type="checkbox" - checked={this.state.permissions.includes(permission)} - name={`permission-${permission}`} - id={`${user.id}-permission-${permission}`} - onChange={this.handlePermissionsChange} - value={permission} - /> - </label> - </td> - ))} - <td> - <SickButton - type="button" - disabled={loading} - onClick={async () => { - await updatePermissions({ - variables: { - permissions: this.state.permissions, - userId: this.props.user.id, - }, - }); - }} - > - Updat{loading ? 'ing' : 'e'} - </SickButton> - </td> - </tr> - )} - </Mutation> - ); - } -} - -const Permissions = () => ( - <Query query={ALL_USERS_QUERY}> - {({ data, error, loading }) => { - if (loading) return <div>Loading</div>; - if (error) return <Error error={error} />; - return ( - <div> - <h1>Manage User Permissions</h1> - <Table> - <thead> - <tr> - <th>Name</th> - <th>Email</th> - {possiblePermissions.map(p => <th>{p}</th>)} - <th>👇🏻</th> - </tr> - </thead> - <tbody>{data.users.map(user => <User key={user.id} user={user} />)}</tbody> - </Table> - </div> - ); - }} - </Query> -); - -export default Permissions; -export { ALL_USERS_QUERY, UPDATE_PERMISSIONS_MUTATION }; diff --git a/finished-application/frontend/components/PleaseSignIn.js b/finished-application/frontend/components/PleaseSignIn.js deleted file mode 100644 index 3cd7267..0000000 --- a/finished-application/frontend/components/PleaseSignIn.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Query } from 'react-apollo'; -import PropTypes from 'prop-types'; -import { CURRENT_USER_QUERY } from './User'; -import Signin from './Signin'; - -const PleaseSignIn = props => ( - <Query query={CURRENT_USER_QUERY}> - {({ data, loading }) => { - if (loading) return <p>Loading...</p>; - // check if they are signed in - if (!data.me) { - return ( - <div> - <p>Please sign in before continuing!</p> - <Signin /> - </div> - ); - } - // check if they need permissions - if (props.allowedPermissions) { - // check if they have NO permissions, or they don't meet the requmrenets - if ( - !data.me.permissions || - !props.allowedPermissions.some(permission => data.me.permissions.includes(permission)) - ) { - return ( - <p> - Insufficient Permissions. You have: - <strong>{data.me.permissions}</strong> - and you need - <strong>{props.allowedPermissions.join(' OR ')}</strong> - </p> - ); - } - } - return props.children; - }} - </Query> -); - -PleaseSignIn.propTypes = { - allowedPermissions: PropTypes.array, - children: PropTypes.any.isRequired, -}; - -export default PleaseSignIn; diff --git a/finished-application/frontend/components/RemoveFromCart.js b/finished-application/frontend/components/RemoveFromCart.js deleted file mode 100644 index cf5b6fd..0000000 --- a/finished-application/frontend/components/RemoveFromCart.js +++ /dev/null @@ -1,57 +0,0 @@ -import { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import styled from 'styled-components'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import { CURRENT_USER_QUERY } from './User'; - -const REMOVE_FROM_CART_MUTATION = gql` - mutation removeFromCart($id: ID!) { - removeFromCart(id: $id) { - id - } - } -`; - -const BigButton = styled.button` - font-size: 3rem; - background: none; - border: 0; - &:hover { - color: ${props => props.theme.red}; - cursor: pointer; - } -`; - -class RemoveFromCart extends Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - - update = (cache, payload) => { - const data = cache.readQuery({ query: CURRENT_USER_QUERY }); - // console.log(data.me.cart[0]); - const cartItemId = payload.data.removeFromCart.id; - data.me.cart = data.me.cart.filter(cartItem => cartItem.id !== cartItemId); - cache.writeQuery({ query: CURRENT_USER_QUERY, data }); - }; - - render() { - return ( - <Mutation - mutation={REMOVE_FROM_CART_MUTATION} - variables={{ id: this.props.id }} - update={this.update} - > - {(removeFromCart, { loading }) => ( - <BigButton disabled={loading} title="Remove From Cart" onClick={removeFromCart}> - × - </BigButton> - )} - </Mutation> - ); - } -} - -export default RemoveFromCart; -export { REMOVE_FROM_CART_MUTATION }; diff --git a/finished-application/frontend/components/Reset.js b/finished-application/frontend/components/Reset.js deleted file mode 100644 index bb2ece0..0000000 --- a/finished-application/frontend/components/Reset.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import Form from './styles/Form'; -import Error from './ErrorMessage'; -import { CURRENT_USER_QUERY } from './User'; - -const RESET_MUTATION = gql` - mutation RESET_MUTATION($resetToken: String!, $password: String!, $confirmPassword: String!) { - resetPassword(resetToken: $resetToken, password: $password, confirmPassword: $confirmPassword) { - id - email - name - } - } -`; - -class Reset extends React.Component { - static propTypes = { - resetToken: PropTypes.string.isRequired, - }; - - state = { - confirmPassword: '', - password: '', - }; - - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); - }; - - resetPassword = async (e, resetMutation) => { - e.preventDefault(); - await resetMutation(); - }; - - render() { - return ( - <Mutation - mutation={RESET_MUTATION} - variables={{ - resetToken: this.props.resetToken, - password: this.state.password, - confirmPassword: this.state.confirmPassword, - }} - refetchQueries={[{ query: CURRENT_USER_QUERY }]} - > - {(resetMutation, { error, loading }) => ( - <Form onSubmit={e => this.resetPassword(e, resetMutation)}> - <Error error={error} /> - <fieldset disabled={loading} aria-busy={loading}> - <label htmlFor="password"> - password - <input - value={this.state.password} - onChange={this.saveToState} - name="password" - type="password" - id="password" - /> - </label> - <label htmlFor="confirm"> - Confirm: - <input - value={this.state.confirmPassword} - onChange={this.saveToState} - name="confirmPassword" - type="password" - id="confirmPassword" - /> - </label> - - <button type="submit">Request Reset!</button> - </fieldset> - </Form> - )} - </Mutation> - ); - } -} - -export default Reset; -export { RESET_MUTATION }; diff --git a/finished-application/frontend/components/ResetRequest.js b/finished-application/frontend/components/ResetRequest.js deleted file mode 100644 index 6aedcc1..0000000 --- a/finished-application/frontend/components/ResetRequest.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { Mutation } from 'react-apollo'; -import gql from 'graphql-tag'; -import Form from './styles/Form'; -import Error from './ErrorMessage'; - -const REQUEST_RESET_MUTATION = gql` - mutation requestReset($email: String!) { - requestReset(email: $email) { - id - } - } -`; - -class ResetRequest extends React.Component { - state = { - email: '', - }; - - render() { - return ( - <Mutation mutation={REQUEST_RESET_MUTATION} variables={this.state}> - {(resetMutation, { loading, error, called }) => ( - <Form - onSubmit={async e => { - e.preventDefault(); - await resetMutation(); - }} - data-test="ResetRequest" - > - <Error error={error} /> - {!error && called && !loading && <p>Success! Check Your Email!</p>} - <fieldset disabled={loading} aria-busy={loading}> - <label htmlFor="email"> - Email - <input - value={this.state.email} - onChange={e => this.setState({ email: e.target.value })} - name="email" - type="text" - placeholder="email" - /> - </label> - - <button type="submit">Request Reset!</button> - </fieldset> - </Form> - )} - </Mutation> - ); - } -} - -export default ResetRequest; -export { REQUEST_RESET_MUTATION }; diff --git a/finished-application/frontend/components/Search.js b/finished-application/frontend/components/Search.js deleted file mode 100644 index dc1bfea..0000000 --- a/finished-application/frontend/components/Search.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import Downshift from 'downshift'; -import Router from 'next/router'; -import { ApolloConsumer } from 'react-apollo'; -import gql from 'graphql-tag'; -import debounce from 'lodash.debounce'; -import { DropDown, DropDownItem, SearchStyles } from './styles/DropDown'; - -const SEARCH_ITEMS_QUERY = gql` - query SEARCH_ITEMS_QUERY($searchTerm: String!) { - items(where: { OR: [{ title_contains: $searchTerm }, { description_contains: $searchTerm }] }) { - id - image - title - } - } -`; -function routeToItem(item) { - Router.push({ - pathname: '/item', - query: { - id: item.id, - }, - }); -} - -class AutoComplete extends React.Component { - state = { - items: [], - loading: false, - }; - onChange = async (e, client) => { - if (!e.target.value) { - return this.setState({ items: [] }); - } - this.setState({ loading: true }); - this.search(client, e.target.value); - }; - - search = debounce(async (client, searchTerm) => { - const res = await client.query({ - query: SEARCH_ITEMS_QUERY, - variables: { searchTerm }, - }); - this.setState({ items: res.data.items, loading: false }); - }, 350); - - render() { - return ( - <SearchStyles> - <Downshift onChange={routeToItem} itemToString={i => (i === null ? '' : i.title)}> - {({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex }) => ( - <div> - {/* This is the searchInput */} - <ApolloConsumer> - {client => ( - <input - {...getInputProps({ - placeholder: 'Search For Item', - id: 'search', - className: this.state.loading ? 'loading' : '', - onChange: e => { - e.persist(); - this.onChange(e, client); - }, - })} - /> - )} - </ApolloConsumer> - {/* This is the Dropdown */} - {isOpen && ( - <DropDown> - {this.state.items.map((item, index) => ( - <DropDownItem - {...getItemProps({ item })} - key={item.id} - highlighted={highlightedIndex === index} - > - <img width="50" src={item.image} alt={item.title} /> - {item.title} - </DropDownItem> - ))} - {/* Found Nothing State */} - {!this.state.items.length && - !this.state.loading && ( - <DropDownItem>Nothing Found for {inputValue}...</DropDownItem> - )} - </DropDown> - )} - </div> - )} - </Downshift> - </SearchStyles> - ); - } -} - -export default AutoComplete; -export { SEARCH_ITEMS_QUERY }; diff --git a/finished-application/frontend/components/Signin.js b/finished-application/frontend/components/Signin.js deleted file mode 100644 index 53cdb76..0000000 --- a/finished-application/frontend/components/Signin.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import gql from 'graphql-tag'; -import { CURRENT_USER_QUERY } from './User'; -import Error from './ErrorMessage'; -import Form from './styles/Form'; - -const SIGNIN_MUTATION = gql` - mutation SIGNIN_MUTATION($email: String!, $password: String!) { - signin(email: $email, password: $password) { - id - email - name - } - } -`; - -class Signin extends Component { - state = { - email: '', - password: '', - }; - - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); - }; - - render() { - return ( - <Mutation - mutation={SIGNIN_MUTATION} - variables={this.state} - refetchQueries={[{ query: CURRENT_USER_QUERY }]} - > - {(signin, { loading, error }) => ( - <Form - onSubmit={e => { - e.preventDefault(); - signin(); - }} - > - <Error error={error} /> - <fieldset disabled={loading} aria-busy={loading}> - <label htmlFor="email"> - Email - <input - value={this.state.email} - onChange={this.saveToState} - name="email" - type="text" - placeholder="email" - /> - </label> - - <label htmlFor="password"> - Password - <input - type="password" - name="password" - id="password" - className="password" - placeholder="password" - value={this.state.password} - onChange={this.saveToState} - /> - </label> - - <button type="submit">Sign In!</button> - </fieldset> - </Form> - )} - </Mutation> - ); - } -} - -export default Signin; -export { SIGNIN_MUTATION }; diff --git a/finished-application/frontend/components/Signout.js b/finished-application/frontend/components/Signout.js deleted file mode 100644 index ae87631..0000000 --- a/finished-application/frontend/components/Signout.js +++ /dev/null @@ -1,25 +0,0 @@ -import React, { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import gql from 'graphql-tag'; -import { CURRENT_USER_QUERY } from './User'; - -const SIGN_OUT_MUTATION = gql` - mutation SIGN_OUT_MUTATION { - signout { - message - } - } -`; - -class Signout extends Component { - render() { - return ( - <Mutation mutation={SIGN_OUT_MUTATION} refetchQueries={[{ query: CURRENT_USER_QUERY }]}> - {signout => <button onClick={signout}>Sign Out</button>} - </Mutation> - ); - } -} - -export default Signout; -export { SIGN_OUT_MUTATION }; diff --git a/finished-application/frontend/components/Signup.js b/finished-application/frontend/components/Signup.js deleted file mode 100644 index 9164e67..0000000 --- a/finished-application/frontend/components/Signup.js +++ /dev/null @@ -1,93 +0,0 @@ -import React, { Component } from 'react'; -import { Mutation } from 'react-apollo'; -import gql from 'graphql-tag'; -import { CURRENT_USER_QUERY } from './User'; -import Form from './styles/Form'; -import Error from './ErrorMessage'; - -const SIGNUP_MUTATION = gql` - mutation SIGNUP_MUTATION($email: String!, $name: String!, $password: String!) { - signup(email: $email, name: $name, password: $password) { - id - email - name - } - } -`; - -class Signup extends Component { - state = { - email: '', - name: '', - password: '', - }; - - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); - }; - - render() { - return ( - <Mutation - mutation={SIGNUP_MUTATION} - variables={this.state} - refetchQueries={[{ query: CURRENT_USER_QUERY }]} - > - {(signup, { loading, error }) => ( - <Form - method="post" - onSubmit={async e => { - e.preventDefault(); - const res = await signup(); - }} - > - <fieldset disabled={loading} aria-busy={loading}> - <Error error={error} /> - <h2>Sign Up for an Account</h2> - <label htmlFor="email"> - Email - <input - value={this.state.email} - onChange={this.saveToState} - name="email" - type="text" - placeholder="email" - /> - </label> - - <label htmlFor="name"> - Name - <input - type="text" - name="name" - placeholder="name" - value={this.state.name} - onChange={this.saveToState} - /> - </label> - - <label htmlFor="signupPassword"> - Password - <input - type="password" - name="password" - id="signupPassword" - className="password" - placeholder="password" - value={this.state.password} - onChange={this.saveToState} - /> - </label> - - <button type="submit">Submit</button> - </fieldset> - </Form> - )} - </Mutation> - ); - } -} - -export default Signup; -export { SIGNUP_MUTATION }; diff --git a/finished-application/frontend/components/SingleItem.js b/finished-application/frontend/components/SingleItem.js deleted file mode 100644 index 83ef8a0..0000000 --- a/finished-application/frontend/components/SingleItem.js +++ /dev/null @@ -1,77 +0,0 @@ -import { Query } from 'react-apollo'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import Head from 'next/head'; -import Link from 'next/link'; -import gql from 'graphql-tag'; -import Error from './ErrorMessage'; -import AddToCart from './AddToCart'; - -const SINGLE_ITEM_QUERY = gql` - query SINGLE_ITEM_QUERY($id: ID!) { - items(where: { id: $id }) { - id - title - description - largeImage - image - } - } -`; - -const SingleItemStyles = styled.div` - max-width: 1200px; - margin: 2rem auto; - box-shadow: ${props => props.theme.bs}; - display: grid; - grid-auto-columns: 1fr; - grid-auto-flow: column; - min-height: 800px; - img { - width: 100%; - height: 100%; - object-fit: contain; - } - .details { - margin: 3rem; - font-size: 2rem; - } -`; - -const SingleItem = props => ( - <Query query={SINGLE_ITEM_QUERY} variables={{ id: props.id }}> - {({ data, loading, error }) => { - if (loading) return <p>Loading...</p>; - if (error) return <Error error={error} />; - const [item] = data.items; - return ( - <SingleItemStyles data-test="SingleItem"> - <Head> - <title>{item.title}</title> - </Head> - <img src={item.largeImage || item.image} alt={item.title} /> - <div className="details"> - <h2>Viewing {item.title}</h2> - <p>{item.description}</p> - <Link - href={{ - pathname: '/update', - query: { id: item.id }, - }} - > - <a>Edit ✏️</a> - </Link> - <AddToCart id={item.id} /> - </div> - </SingleItemStyles> - ); - }} - </Query> -); - -SingleItem.propTypes = { - id: PropTypes.string.isRequired, -}; - -export default SingleItem; -export { SINGLE_ITEM_QUERY }; diff --git a/finished-application/frontend/components/TakeMyMoney.js b/finished-application/frontend/components/TakeMyMoney.js deleted file mode 100644 index b9e42ea..0000000 --- a/finished-application/frontend/components/TakeMyMoney.js +++ /dev/null @@ -1,83 +0,0 @@ -import { Component } from 'react'; -import StripeCheckout from 'react-stripe-checkout'; -import { Mutation, Query } from 'react-apollo'; -import Router from 'next/router'; -import NProgress from 'nprogress'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import calcTotalPrice from '../lib/calcTotalPrice'; -import Error from './ErrorMessage'; -import User, { CURRENT_USER_QUERY } from './User'; - -const CREATE_ORDER_MUTATION = gql` - mutation createOrder($token: String!) { - createOrder(token: $token) { - id - charge - total - items { - id - title - } - } - } -`; - -function totalItems(cart) { - return cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); -} - -class TakeMyMoney extends Component { - onToken = async (res, createOrder) => { - NProgress.start(); - const order = await createOrder({ - variables: { - token: res.id, - }, - }); - console.log(order); - // Route them to that order page - const { id } = order.data.createOrder; - Router.push({ - pathname: `/order`, - query: { id }, - }); - }; - render() { - return ( - <User> - {({ data: { me }, error }) => { - if (!me || !me.cart.length) return null; - if (error) return <Error error={error} />; - return ( - <Mutation - mutation={CREATE_ORDER_MUTATION} - refetchQueries={[{ query: CURRENT_USER_QUERY }]} - > - {createOrder => ( - <StripeCheckout - amount={calcTotalPrice(me.cart)} - name="Sick Fits Haul" - description={`Order of ${totalItems(me.cart)} Items From Sick Fits`} - image={me.cart[0].item && me.cart[0].item.image} - token={res => this.onToken(res, createOrder)} - stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps" - currency="USD" - email={me.email} - > - {this.props.children} - </StripeCheckout> - )} - </Mutation> - ); - }} - </User> - ); - } -} - -TakeMyMoney.propTypes = { - children: PropTypes.any, -}; - -export default TakeMyMoney; diff --git a/finished-application/frontend/components/UpdateItem.js b/finished-application/frontend/components/UpdateItem.js deleted file mode 100644 index 08ba254..0000000 --- a/finished-application/frontend/components/UpdateItem.js +++ /dev/null @@ -1,105 +0,0 @@ -import React, { Component } from 'react'; -import { Query, Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; -import gql from 'graphql-tag'; -import { SINGLE_ITEM_QUERY } from './SingleItem'; -import Form from './styles/Form'; -import Error from './ErrorMessage'; - -const UPDATE_ITEM_MUTATION = gql` - mutation updateItem($id: ID!, $title: String, $description: String, $price: Int) { - updateItem(id: $id, description: $description, title: $title, price: $price) { - id - } - } -`; - -class UpdateItem extends Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - state = { - item: {}, - }; - - saveToState = e => { - let { name, value, type } = e.target; - if (type === 'number') { - value = parseInt(value); - } - const item = { ...this.state.item }; - item[name] = value; - this.setState({ item }); - }; - - updateItem = async (e, updateItemMutation) => { - console.log('Updating Item'); - e.preventDefault(); - - const res = await updateItemMutation({ - // pass in those variables from state - variables: { - id: this.props.id, - ...this.state.item, - }, - }); - console.log(res); - }; - - render() { - return ( - <Query query={SINGLE_ITEM_QUERY} variables={{ id: this.props.id }}> - {({ data: { items }, loading }) => { - if (loading) return <p>Loading...</p>; - if (!items || !items.length) return <p>Item Not Found</p>; - const [item] = items; - return ( - <Mutation mutation={UPDATE_ITEM_MUTATION}> - {(updateItemMutation, { error }) => ( - <Form onSubmit={e => this.updateItem(e, updateItemMutation)}> - <Error error={error} /> - <h2>Edit {item.title}</h2> - <fieldset disabled={loading} aria-busy={loading}> - <label htmlFor="title"> - Title - <input - id="title" - defaultValue={item.title} - name="title" - onChange={this.saveToState} - type="text" - /> - </label> - - <label htmlFor="description"> - Description - <textarea - defaultValue={item.description} - name="description" - onChange={this.saveToState} - /> - </label> - - <label htmlFor="price"> - Price - <input - type="number" - name="price" - onChange={this.saveToState} - defaultValue={item.price} - /> - </label> - <button type="submit">Save...</button> - </fieldset> - </Form> - )} - </Mutation> - ); - }} - </Query> - ); - } -} - -export default UpdateItem; -export { UPDATE_ITEM_MUTATION }; diff --git a/finished-application/frontend/components/User.js b/finished-application/frontend/components/User.js deleted file mode 100644 index 19c568d..0000000 --- a/finished-application/frontend/components/User.js +++ /dev/null @@ -1,45 +0,0 @@ -import { Query } from 'react-apollo'; -import gql from 'graphql-tag'; -import PropTypes from 'prop-types'; - -const CURRENT_USER_QUERY = gql` - query { - me { - id - email - name - permissions - orders { - id - charge - total - } - cart { - id - quantity - item { - __typename - id - title - price - description - image - largeImage - } - } - } - } -`; - -const User = props => ( - <Query {...props} query={CURRENT_USER_QUERY}> - {result => props.children(result)} - </Query> -); - -User.propTypes = { - children: PropTypes.func.isRequired, -}; - -export default User; -export { CURRENT_USER_QUERY }; diff --git a/finished-application/frontend/components/styles/CartStyles.js b/finished-application/frontend/components/styles/CartStyles.js deleted file mode 100644 index d5ee93a..0000000 --- a/finished-application/frontend/components/styles/CartStyles.js +++ /dev/null @@ -1,47 +0,0 @@ -import styled from 'styled-components'; - -const CartStyles = styled.div` - padding: 20px; - position: relative; - background: white; - position: fixed; - 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);`}; - 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; - } - } - ul { - margin: 0; - padding: 0; - list-style: none; - overflow: scroll; - } -`; - -export default CartStyles; diff --git a/finished-application/frontend/components/styles/CloseButton.js b/finished-application/frontend/components/styles/CloseButton.js deleted file mode 100644 index 69fd55c..0000000 --- a/finished-application/frontend/components/styles/CloseButton.js +++ /dev/null @@ -1,13 +0,0 @@ -import styled from 'styled-components'; - -const CloseButton = styled.button` - background: black; - color: white; - font-size: 3rem; - border: 0; - position: absolute; - z-index: 2; - right: 0; -`; - -export default CloseButton; diff --git a/finished-application/frontend/components/styles/DropDown.js b/finished-application/frontend/components/styles/DropDown.js deleted file mode 100644 index df86aee..0000000 --- a/finished-application/frontend/components/styles/DropDown.js +++ /dev/null @@ -1,47 +0,0 @@ -import styled, { keyframes } from 'styled-components'; - -const DropDown = styled.div` - position: absolute; - width: 100%; - z-index: 2; - border: 1px solid ${props => props.theme.lightgrey}; -`; - -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 glow = keyframes` - from { - box-shadow: 0 0 0px yellow; - } - - to { - box-shadow: 0 0 10px 1px yellow; - } -`; - -const SearchStyles = styled.div` - position: relative; - input { - width: 100%; - padding: 10px; - border: 0; - font-size: 2rem; - &.loading { - animation: ${glow} 0.5s ease-in-out infinite alternate; - } - } -`; - -export { DropDown, DropDownItem, SearchStyles }; diff --git a/finished-application/frontend/components/styles/Form.js b/finished-application/frontend/components/styles/Form.js deleted file mode 100644 index 5717130..0000000 --- a/finished-application/frontend/components/styles/Form.js +++ /dev/null @@ -1,71 +0,0 @@ -import styled, { keyframes } from 'styled-components'; - -const loading = keyframes` - from { - background-position: 0 0; - /* rotate: 0; */ - } - - to { - background-position: 100% 100%; - /* rotate: 360deg; */ - } -`; - -const Form = styled.form` - box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.05); - background: rgba(0, 0, 0, 0.02); - border: 5px solid white; - padding: 20px; - font-size: 1.5rem; - line-height: 1.5; - font-weight: 600; - label { - display: block; - margin-bottom: 1rem; - } - input, - textarea, - select { - width: 100%; - padding: 0.5rem; - font-size: 1rem; - border: 1px solid black; - &:focus { - outline: 0; - border-color: ${props => props.theme.red}; - } - } - button, - input[type='submit'] { - width: auto; - background: red; - color: white; - border: 0; - font-size: 2rem; - font-weight: 600; - padding: 0.5rem 1.2rem; - } - fieldset { - border: 0; - padding: 0; - - &[disabled] { - opacity: 0.5; - } - &::before { - height: 10px; - content: ''; - display: block; - background-image: linear-gradient(to right, #ff3019 0%, #e2b04a 50%, #ff3019 100%); - } - &[aria-busy='true']::before { - background-size: 50% auto; - animation: ${loading} 0.5s linear infinite; - } - } -`; - -Form.displayName = 'Form'; - -export default Form; diff --git a/finished-application/frontend/components/styles/ItemStyles.js b/finished-application/frontend/components/styles/ItemStyles.js deleted file mode 100644 index b117d5f..0000000 --- a/finished-application/frontend/components/styles/ItemStyles.js +++ /dev/null @@ -1,39 +0,0 @@ -import styled from 'styled-components'; - -const Item = styled.div` - background: white; - border: 1px solid ${props => props.theme.offWhite}; - box-shadow: ${props => props.theme.bs}; - position: relative; - display: flex; - flex-direction: column; - img { - width: 100%; - height: 400px; - object-fit: cover; - } - p { - font-size: 12px; - line-height: 2; - font-weight: 300; - flex-grow: 1; - padding: 0 3rem; - font-size: 1.5rem; - } - .buttonList { - display: grid; - width: 100%; - 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}; - & > * { - background: white; - border: 0; - font-size: 1rem; - padding: 1rem; - } - } -`; - -export default Item; diff --git a/finished-application/frontend/components/styles/NavStyles.js b/finished-application/frontend/components/styles/NavStyles.js deleted file mode 100644 index 41523df..0000000 --- a/finished-application/frontend/components/styles/NavStyles.js +++ /dev/null @@ -1,64 +0,0 @@ -import styled from 'styled-components'; - -const NavStyles = styled.ul` - margin: 0; - padding: 0; - display: flex; - justify-self: end; - font-size: 2rem; - a, - button { - padding: 1rem 3rem; - display: flex; - align-items: center; - position: relative; - text-transform: uppercase; - font-weight: 900; - font-size: 1em; - background: none; - border: 0; - cursor: pointer; - @media (max-width: 700px) { - font-size: 10px; - padding: 0 10px; - } - &:before { - content: ''; - width: 2px; - background: ${props => props.theme.lightgrey}; - height: 100%; - left: 0; - position: absolute; - transform: skew(-20deg); - top: 0; - bottom: 0; - } - &:after { - height: 2px; - background: red; - content: ''; - width: 0; - position: absolute; - transform: translateX(-50%); - transition: width 0.4s; - 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); - } - } - } - @media (max-width: 1300px) { - border-top: 1px solid ${props => props.theme.lightgrey}; - width: 100%; - justify-content: center; - font-size: 1.5rem; - } -`; - -export default NavStyles; diff --git a/finished-application/frontend/components/styles/OrderItemStyles.js b/finished-application/frontend/components/styles/OrderItemStyles.js deleted file mode 100644 index 292c75d..0000000 --- a/finished-application/frontend/components/styles/OrderItemStyles.js +++ /dev/null @@ -1,44 +0,0 @@ -import styled from 'styled-components'; - -const OrderItemStyles = styled.li` - box-shadow: ${props => props.theme.bs}; - list-style: none; - padding: 2rem; - border: 1px solid ${props => props.theme.offWhite}; - h2 { - border-bottom: 2px solid red; - margin-top: 0; - margin-bottom: 2rem; - padding-bottom: 2rem; - } - - .images { - display: grid; - grid-gap: 10px; - grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); - margin-top: 1rem; - img { - height: 200px; - object-fit: cover; - width: 100%; - } - } - .order-meta { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(20px, 1fr)); - display: grid; - grid-gap: 1rem; - text-align: center; - & > * { - margin: 0; - background: rgba(0, 0, 0, 0.03); - padding: 1rem 0; - } - strong { - display: block; - margin-bottom: 1rem; - } - } -`; - -export default OrderItemStyles; diff --git a/finished-application/frontend/components/styles/OrderStyles.js b/finished-application/frontend/components/styles/OrderStyles.js deleted file mode 100644 index 4a2d804..0000000 --- a/finished-application/frontend/components/styles/OrderStyles.js +++ /dev/null @@ -1,38 +0,0 @@ -import styled from 'styled-components'; - -const OrderStyles = styled.div` - max-width: 1000px; - margin: 0 auto; - border: 1px solid ${props => props.theme.offWhite}; - box-shadow: ${props => props.theme.bs}; - padding: 2rem; - border-top: 10px solid red; - & > p { - display: grid; - grid-template-columns: 1fr 5fr; - margin: 0; - border-bottom: 1px solid ${props => props.theme.offWhite}; - span { - padding: 1rem; - &:first-child { - font-weight: 900; - text-align: right; - } - } - } - .order-item { - border-bottom: 1px solid ${props => props.theme.offWhite}; - display: grid; - grid-template-columns: 300px 1fr; - align-items: center; - grid-gap: 2rem; - margin: 2rem 0; - padding-bottom: 2rem; - img { - width: 100%; - height: 100%; - object-fit: cover; - } - } -`; -export default OrderStyles; diff --git a/finished-application/frontend/components/styles/PaginationStyles.js b/finished-application/frontend/components/styles/PaginationStyles.js deleted file mode 100644 index 30753af..0000000 --- a/finished-application/frontend/components/styles/PaginationStyles.js +++ /dev/null @@ -1,27 +0,0 @@ -import styled from 'styled-components'; - -const PaginationStyles = styled.div` - text-align: center; - display: inline-grid; - grid-template-columns: repeat(4, auto); - align-items: stretch; - justify-content: center; - align-content: center; - margin: 2rem 0; - border: 1px solid ${props => props.theme.lightgrey}; - border-radius: 10px; - & > * { - margin: 0; - padding: 15px 30px; - border-right: 1px solid ${props => props.theme.lightgrey}; - &:last-child { - border-right: 0; - } - } - a[aria-disabled='true'] { - color: grey; - pointer-events: none; - } -`; - -export default PaginationStyles; diff --git a/finished-application/frontend/components/styles/PriceTag.js b/finished-application/frontend/components/styles/PriceTag.js deleted file mode 100644 index 9116681..0000000 --- a/finished-application/frontend/components/styles/PriceTag.js +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'styled-components'; - -const PriceTag = styled.span` - background: ${props => props.theme.red}; - transform: rotate(3deg); - color: white; - font-weight: 600; - padding: 5px; - line-height: 1; - font-size: 3rem; - display: inline-block; - position: absolute; - top: -3px; - right: -3px; -`; - -export default PriceTag; diff --git a/finished-application/frontend/components/styles/SickButton.js b/finished-application/frontend/components/styles/SickButton.js deleted file mode 100644 index 5b5352e..0000000 --- a/finished-application/frontend/components/styles/SickButton.js +++ /dev/null @@ -1,20 +0,0 @@ -import styled from 'styled-components'; - -const SickButton = styled.button` - background: red; - color: white; - font-weight: 500; - border: 0; - border-radius: 0; - text-transform: uppercase; - font-size: 2rem; - padding: 0.8rem 1.5rem; - transform: skew(-2deg); - display: inline-block; - transition: all 0.5s; - &[disabled] { - opacity: 0.5; - } -`; - -export default SickButton; diff --git a/finished-application/frontend/components/styles/Supreme.js b/finished-application/frontend/components/styles/Supreme.js deleted file mode 100644 index d946ccd..0000000 --- a/finished-application/frontend/components/styles/Supreme.js +++ /dev/null @@ -1,13 +0,0 @@ -import styled from 'styled-components'; - -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; -`; - -export default Supreme; diff --git a/finished-application/frontend/components/styles/Table.js b/finished-application/frontend/components/styles/Table.js deleted file mode 100644 index e9d0673..0000000 --- a/finished-application/frontend/components/styles/Table.js +++ /dev/null @@ -1,31 +0,0 @@ -import styled from 'styled-components'; - -const Table = styled.table` - border-spacing: 0; - width: 100%; - border: 1px solid ${props => props.theme.offWhite}; - thead { - font-size: 10px; - } - td, - th { - border-bottom: 1px solid ${props => props.theme.offWhite}; - border-right: 1px solid ${props => props.theme.offWhite}; - padding: 10px 5px; - position: relative; - &:last-child { - border-right: none; - width: 150px; - button { - width: 100%; - } - } - } - tr { - &:hover { - background: ${props => props.theme.offWhite}; - } - } -`; - -export default Table; diff --git a/finished-application/frontend/components/styles/Title.js b/finished-application/frontend/components/styles/Title.js deleted file mode 100644 index e1901ec..0000000 --- a/finished-application/frontend/components/styles/Title.js +++ /dev/null @@ -1,20 +0,0 @@ -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; |
