diff options
| author | Randy Ridge <randyridge@gmail.com> | 2018-09-14 20:01:25 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-09-14 20:01:25 -0400 |
| commit | 908180c77cd38011eeedcae949613da2a3391e5e (patch) | |
| tree | b59bf1aae819a04d453cde72f6e601f5561a740f /finished-application/frontend/components | |
| parent | 1f6667d233a4a2e9df977204d83a8f749c050c75 (diff) | |
| parent | b3bebda57ba187b7fa10398054b54c05d3fd3555 (diff) | |
Merge branch 'master' into randyridge/our
Diffstat (limited to 'finished-application/frontend/components')
35 files changed, 679 insertions, 947 deletions
diff --git a/finished-application/frontend/components/AddToCart.js b/finished-application/frontend/components/AddToCart.js index 03242a4..8a71cc3 100644 --- a/finished-application/frontend/components/AddToCart.js +++ b/finished-application/frontend/components/AddToCart.js @@ -1,68 +1,36 @@ -import { Component } from 'react'; +import React from 'react'; import { Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; import gql from 'graphql-tag'; -import User, { CURRENT_USER_QUERY } from './User'; +import { 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 }); - }; - +class AddToCart extends React.Component { 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> - ); + <Mutation + mutation={ADD_TO_CART_MUTATION} + variables={{ + id, }} - </User> + refetchQueries={[{ query: CURRENT_USER_QUERY }]} + > + {(addToCart, { loading }) => ( + <button disabled={loading} onClick={addToCart}> + Add{loading && 'ing'} To Cart 🛒 + </button> + )} + </Mutation> ); } } - export default AddToCart; export { ADD_TO_CART_MUTATION }; diff --git a/finished-application/frontend/components/Cart.js b/finished-application/frontend/components/Cart.js index 7e8a9c5..2a72b38 100644 --- a/finished-application/frontend/components/Cart.js +++ b/finished-application/frontend/components/Cart.js @@ -1,17 +1,16 @@ import React from 'react'; -import { Mutation, Query } from 'react-apollo'; -import { adopt } from 'react-adopt'; +import { Query, Mutation } from 'react-apollo'; 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 { adopt } from 'react-adopt'; +import User from './User'; import CartStyles from './styles/CartStyles'; import Supreme from './styles/Supreme'; import CloseButton from './styles/CloseButton'; import SickButton from './styles/SickButton'; +import CartItem from './CartItem'; +import calcTotalPrice from '../lib/calcTotalPrice'; +import formatMoney from '../lib/formatMoney'; +import TakeMyMoney from './TakeMyMoney'; const LOCAL_STATE_QUERY = gql` query { @@ -24,45 +23,38 @@ const TOGGLE_CART_MUTATION = gql` toggleCart @client } `; - +/* eslint-disable */ 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" /> - ), + user: ({ render }) => <User>{render}</User>, + toggleCart: ({ render }) => <Mutation mutation={TOGGLE_CART_MUTATION}>{render}</Mutation>, + localState: ({ render }) => <Query query={LOCAL_STATE_QUERY}>{render}</Query>, }); +/* eslint-enable */ const Cart = () => ( <Composed> - {({ toggleCart, localState, currentUser }) => { - const { data: { me }, error, loading } = currentUser; - if (loading) return <p>Loading...</p>; - if (error) return <Error error={error} />; + {({ user, toggleCart, localState }) => { + const me = user.data.me; if (!me) return null; return ( <CartStyles open={localState.data.cartOpen}> <header> - <CloseButton title="close" onClick={toggleCart.mutate}> + <CloseButton onClick={toggleCart} title="close"> × </CloseButton> - - <Supreme>{me.name}'s Cart.</Supreme> + <Supreme>{me.name}'s Cart</Supreme> <p> - You have {me.cart.length} item{me.cart.length === 1 ? '' : 's'} in your cart. + 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> + {me.cart.length && ( + <TakeMyMoney> + <SickButton>Checkout</SickButton> + </TakeMyMoney> + )} </footer> </CartStyles> ); diff --git a/finished-application/frontend/components/CartCount.js b/finished-application/frontend/components/CartCount.js index ae51f20..c202d74 100644 --- a/finished-application/frontend/components/CartCount.js +++ b/finished-application/frontend/components/CartCount.js @@ -1,19 +1,7 @@ -import styled from 'styled-components'; -import { TransitionGroup, CSSTransition } from 'react-transition-group'; +import React from 'react'; 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; -`; +import { TransitionGroup, CSSTransition } from 'react-transition-group'; +import styled from 'styled-components'; const AnimationStyles = styled.span` position: relative; @@ -23,25 +11,36 @@ const AnimationStyles = styled.span` transition: all 0.4s; backface-visibility: hidden; } + /* Intial State of the entered Dot */ .count-enter { - transform: rotateX(0.5turn); + transform: scale(4) 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); + transform: scale(4) rotateX(0.5turn); } `; +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 CartCount = ({ count }) => ( <AnimationStyles> <TransitionGroup> @@ -58,7 +57,4 @@ const CartCount = ({ count }) => ( </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 index 6c6d575..b0ce62e 100644 --- a/finished-application/frontend/components/CartItem.js +++ b/finished-application/frontend/components/CartItem.js @@ -1,3 +1,4 @@ +import React from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import formatMoney from '../lib/formatMoney'; @@ -12,30 +13,29 @@ const CartItemStyles = styled.li` img { margin-right: 10px; } - h3 { - margin: 0; - } + h3, p { margin: 0; } `; const CartItem = ({ cartItem }) => { + // first check if that item exists if (!cartItem.item) return ( - <CartItemStyles key={cartItem.id}> - <p>Ack! That Item is gone!</p> + <CartItemStyles> + <p>This Item has been removed</p> <RemoveFromCart id={cartItem.id} /> </CartItemStyles> ); return ( - <CartItemStyles key={cartItem.id}> + <CartItemStyles> <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)} - {' — '} + {formatMoney(cartItem.item.price * cartItem.quantity)} + {' - '} <em> {cartItem.quantity} × {formatMoney(cartItem.item.price)} each </em> diff --git a/finished-application/frontend/components/CreateItem.js b/finished-application/frontend/components/CreateItem.js index 503b3cb..c115eac 100644 --- a/finished-application/frontend/components/CreateItem.js +++ b/finished-application/frontend/components/CreateItem.js @@ -1,25 +1,22 @@ 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 Router from 'next/router'; import Form from './styles/Form'; import formatMoney from '../lib/formatMoney'; -import { ALL_ITEMS_QUERY } from './Items'; -import { PAGINATION_QUERY } from './Pagination'; +import Error from './ErrorMessage'; const CREATE_ITEM_MUTATION = gql` mutation CREATE_ITEM_MUTATION( - $description: String! $title: String! + $description: String! $price: Int! $image: String $largeImage: String ) { createItem( - description: $description title: $title + description: $description price: $price image: $image largeImage: $largeImage @@ -37,22 +34,19 @@ class CreateItem extends Component { largeImage: '', price: 0, }; - handleChange = e => { - const { name, value, type } = e.target; + const { name, type, value } = 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 files = e.target.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', { + const res = await fetch('https://api.cloudinary.com/v1_1/wesbostutorial/image/upload', { method: 'POST', body: data, }); @@ -60,82 +54,82 @@ class CreateItem extends Component { 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 }]} - > + <Mutation mutation={CREATE_ITEM_MUTATION} variables={this.state}> {(createItem, { loading, error }) => ( <Form - data-test + data-test="form" onSubmit={async e => { + // Stop the form from submitting 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(); + // call the mutation + const res = await createItem(); + // change them to the single item page + console.log(res); Router.push({ - pathname: `/item`, - query: { id: item.id }, + pathname: '/item', + query: { id: res.data.createItem.id }, }); }} > - <h2>Sell an Item.</h2> <Error error={error} /> <fieldset disabled={loading} aria-busy={loading}> <label htmlFor="file"> Image <input - required + type="file" id="file" + name="file" + placeholder="Upload an image" + required onChange={this.uploadFile} - type="file" - accept=".png, .jpg, .jpeg" /> - {this.state.image ? ( - <img src={this.state.image} width="100" alt={this.state.title} /> - ) : null} + {this.state.image && ( + <img width="200" src={this.state.image} alt="Upload Preview" /> + )} </label> + <label htmlFor="title"> Title <input - required - value={this.state.title} - onChange={this.handleChange} type="text" - name="title" id="title" + name="title" placeholder="Title" + required + value={this.state.title} + onChange={this.handleChange} /> </label> + <label htmlFor="price"> - Price {this.state.price && formatMoney(this.state.price)} + Price <input - required type="number" id="price" name="price" - min="0" + placeholder="Price" + required 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> + + <label htmlFor="description"> + Description + <textarea + id="description" + name="description" + placeholder="Enter A Description" + required + value={this.state.description} + onChange={this.handleChange} + /> + </label> + <button type="submit">Submit</button> </fieldset> </Form> )} diff --git a/finished-application/frontend/components/DeleteItem.js b/finished-application/frontend/components/DeleteItem.js index 4d68d5e..e5e4752 100644 --- a/finished-application/frontend/components/DeleteItem.js +++ b/finished-application/frontend/components/DeleteItem.js @@ -1,65 +1,45 @@ -import React from 'react'; +import React, { Component } 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!) { + mutation DELETE_ITEM_MUTATION($id: ID!) { deleteItem(id: $id) { id - title - description } } `; -class DeleteItem extends React.Component { - static propTypes = { - id: PropTypes.string.isRequired, - }; - +class DeleteItem extends Component { 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 + // manually update the cache on the client, so it matches the server + // 1. Read the cache for the items we want + const data = cache.readQuery({ query: ALL_ITEMS_QUERY }); + console.log(data, payload); + // 2. Filter the deleted itemout of the page + data.items = data.items.filter(item => item.id !== payload.data.deleteItem.id); + // 3. Put the items back! + cache.writeQuery({ query: ALL_ITEMS_QUERY, data }); }; - 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 }) => ( + {(deleteItem, { error }) => ( <button onClick={() => { if (confirm('Are you sure you want to delete this item?')) { - removeItem(); + deleteItem().catch(err => { + alert(err.message); + }); } }} > - {error ? error.message : '× Delete Item'} + {this.props.children} </button> )} </Mutation> @@ -67,5 +47,4 @@ class DeleteItem extends React.Component { } } -export default withRouter(DeleteItem); -export { DELETE_ITEM_MUTATION }; +export default DeleteItem; 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/Header.js b/finished-application/frontend/components/Header.js index bb05c34..797eb8e 100644 --- a/finished-application/frontend/components/Header.js +++ b/finished-application/frontend/components/Header.js @@ -1,36 +1,21 @@ -import React from 'react'; +import Link from 'next/link'; +import styled from 'styled-components'; import NProgress from 'nprogress'; import Router from 'next/router'; -import styled from 'styled-components'; -import Link from 'next/link'; +import Nav from './Nav'; import Cart from './Cart'; import Search from './Search'; -import Nav from './Nav'; Router.onRouteChangeStart = () => { NProgress.start(); }; -Router.onRouteChangeComplete = () => NProgress.done(); -Router.onRouteChangeError = () => NProgress.done(); +Router.onRouteChangeComplete = () => { + 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}; - } -`; +Router.onRouteChangeError = () => { + NProgress.done(); +}; const Logo = styled.h1` font-size: 4rem; @@ -42,8 +27,8 @@ const Logo = styled.h1` padding: 0.5rem 1rem; background: ${props => props.theme.red}; color: white; - letter-spacing: -2px; text-transform: uppercase; + text-decoration: none; } @media (max-width: 1300px) { margin: 0; @@ -51,12 +36,31 @@ const Logo = styled.h1` } `; +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 Header = () => ( <StyledHeader> <div className="bar"> <Logo> <Link href="/"> - <a>Sick Fits!</a> + <a>Sick Fits</a> </Link> </Logo> <Nav /> diff --git a/finished-application/frontend/components/Item.js b/finished-application/frontend/components/Item.js index d6b339e..2741dcf 100644 --- a/finished-application/frontend/components/Item.js +++ b/finished-application/frontend/components/Item.js @@ -1,23 +1,24 @@ -import React from 'react'; +import React, { Component } 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'; +import formatMoney from '../lib/formatMoney'; +import DeleteItem from './DeleteItem'; +import AddToCart from './AddToCart'; -class Item extends React.Component { +export default class Item extends Component { static propTypes = { item: PropTypes.object.isRequired, }; render() { - const item = this.props.item; + const { item } = this.props; return ( - <ItemStyles key={item.id}> + <ItemStyles> {item.image && <img src={item.image} alt={item.title} />} + <Title> <Link href={{ @@ -28,26 +29,22 @@ class Item extends React.Component { <a>{item.title}</a> </Link> </Title> - <PriceTag>{formatMoney(item.price)}</PriceTag> - <p>{item.description}</p> <div className="buttonList"> <Link href={{ - pathname: '/update', + pathname: 'update', query: { id: item.id }, }} > <a>Edit ✏️</a> </Link> <AddToCart id={item.id} /> - <DeleteItem id={item.id} /> + <DeleteItem id={item.id}>Delete This Item</DeleteItem> </div> </ItemStyles> ); } } - -export default Item; diff --git a/finished-application/frontend/components/Items.js b/finished-application/frontend/components/Items.js index 5694d0b..9b5426c 100644 --- a/finished-application/frontend/components/Items.js +++ b/finished-application/frontend/components/Items.js @@ -1,17 +1,14 @@ -import React from 'react'; +import React, { Component } 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 styled from 'styled-components'; import Item from './Item'; -import LoadingItem from './LoadingItem'; +import Pagination from './Pagination'; 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 + items(first: $first, skip: $skip, orderBy: createdAt_DESC) { id title price @@ -22,7 +19,11 @@ const ALL_ITEMS_QUERY = gql` } `; -const Items = styled.div` +const Center = styled.div` + text-align: center; +`; + +const ItemsList = styled.div` display: grid; grid-template-columns: 1fr 1fr; grid-gap: 60px; @@ -30,38 +31,24 @@ const Items = styled.div` margin: 0 auto; `; -const Center = styled.div` - text-align: center; -`; - -class ItemList extends React.Component { - static propTypes = { - page: PropTypes.number.isRequired, - }; +class Items extends Component { render() { return ( - <Center key={this.props.page}> + <Center> <Pagination page={this.props.page} /> <Query query={ALL_ITEMS_QUERY} + // fetchPolicy="network-only" 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>; + if (loading) return <p>Loading...</p>; + if (error) return <p>Error: {error.message}</p>; + return ( + <ItemsList>{data.items.map(item => <Item item={item} key={item.id} />)}</ItemsList> + ); }} </Query> <Pagination page={this.props.page} /> @@ -70,5 +57,5 @@ class ItemList extends React.Component { } } -export default ItemList; +export default Items; 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 index fe9c29f..2b98f92 100644 --- a/finished-application/frontend/components/Meta.js +++ b/finished-application/frontend/components/Meta.js @@ -1,16 +1,13 @@ -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> + <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> ); export default Meta; diff --git a/finished-application/frontend/components/Nav.js b/finished-application/frontend/components/Nav.js index b483376..118db5a 100644 --- a/finished-application/frontend/components/Nav.js +++ b/finished-application/frontend/components/Nav.js @@ -1,59 +1,49 @@ -import React, { Fragment } from 'react'; import Link from 'next/link'; -import { Query, Mutation } from 'react-apollo'; +import { Mutation } from 'react-apollo'; import { TOGGLE_CART_MUTATION } from './Cart'; +import NavStyles from './styles/NavStyles'; 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 }, error }) => ( - <NavStyles data-test="nav"> - <Link href="/items"> - <a>Shop</a> - </Link> +const Nav = () => ( + <User> + {({ data: { me } }) => ( + <NavStyles data-test="nav"> + <Link href="/items"> + <a>Shop</a> + </Link> + {me && ( + <> <Link href="/sell"> <a>Sell</a> </Link> + <Link href="/orders"> + <a>Orders</a> + </Link> + <Link href="/me"> + <a>Account</a> + </Link> + <Signout /> + <Mutation mutation={TOGGLE_CART_MUTATION}> + {(toggleCart) => ( + <button onClick={toggleCart}> + My Cart + <CartCount count={me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)}></CartCount> + </button> + )} + </Mutation> + </> + )} + {!me && ( + <Link href="/signup"> + <a>Sign In</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> - ); - } -} + </NavStyles> + )} + </User> +); export default Nav; diff --git a/finished-application/frontend/components/Order.js b/finished-application/frontend/components/Order.js index b4a0a0e..5177c0a 100644 --- a/finished-application/frontend/components/Order.js +++ b/finished-application/frontend/components/Order.js @@ -1,8 +1,8 @@ -import { Component } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; 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'; @@ -21,8 +21,8 @@ const SINGLE_ORDER_QUERY = gql` items { id title - price description + price image quantity } @@ -30,21 +30,16 @@ const SINGLE_ORDER_QUERY = gql` } `; -class Order extends Component { +class Order extends React.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 }) => { + <Query query={SINGLE_ORDER_QUERY} variables={{ id: this.props.id }}> + {({ data, error, loading }) => { + if (error) return <Error error={error} />; if (loading) return <p>Loading...</p>; - if (error) return <Error error={error} refetch={refetch} />; const order = data.order; return ( <OrderStyles data-test="order"> @@ -52,8 +47,8 @@ class Order extends Component { <title>Sick Fits - Order {order.id}</title> </Head> <p> - <span>Order Id:</span> - <span>{order.id}</span> + <span>Order ID:</span> + <span>{this.props.id}</span> </p> <p> <span>Charge</span> @@ -61,7 +56,7 @@ class Order extends Component { </p> <p> <span>Date</span> - <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span> + <span>{format(order.createdAt, 'MMMM d, YYYY h:mm a')}</span> </p> <p> <span>Order Total</span> @@ -76,11 +71,10 @@ class Order extends Component { <div className="order-item" key={item.id}> <img src={item.image} alt={item.title} /> <div className="item-details"> - <h2> {item.title} </h2> + <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>SubTotal: {formatMoney(item.price * item.quantity)}</p> <p>{item.description}</p> </div> </div> diff --git a/finished-application/frontend/components/OrderList.js b/finished-application/frontend/components/OrderList.js index 5886ad7..e2e22ff 100644 --- a/finished-application/frontend/components/OrderList.js +++ b/finished-application/frontend/components/OrderList.js @@ -4,16 +4,16 @@ import { formatDistance } from 'date-fns'; import Link from 'next/link'; import styled from 'styled-components'; import gql from 'graphql-tag'; +import Error from './ErrorMessage'; import formatMoney from '../lib/formatMoney'; import OrderItemStyles from './styles/OrderItemStyles'; const USER_ORDERS_QUERY = gql` - query orders { + query USER_ORDERS_QUERY { orders(orderBy: createdAt_DESC) { id total createdAt - updatedAt items { id title @@ -26,7 +26,7 @@ const USER_ORDERS_QUERY = gql` } `; -const OrderUl = styled.ul` +const orderUl = styled.ul` display: grid; grid-gap: 4rem; grid-template-columns: repeat(auto-fit, minmax(40%, 1fr)); @@ -37,13 +37,13 @@ class OrderList extends React.Component { 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>; + if (loading) return <p>loading...</p>; + if (error) return <Error erorr={error} />; + console.log(orders); return ( <div> - <h2>You have {orders.length} Orders</h2> - <OrderUl> + <h2>You have {orders.length} orders</h2> + <orderUl> {orders.map(order => ( <OrderItemStyles key={order.id}> <Link @@ -54,22 +54,10 @@ class OrderList extends React.Component { > <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> + <p>{order.items.reduce((a, b) => a + b.quantity, 0)} Items</p> + <p>{order.items.length} Products</p> + <p>{formatDistance(order.createdAt, new Date())}</p> + <p>{formatMoney(order.total)}</p> </div> <div className="images"> {order.items.map(item => ( @@ -80,7 +68,7 @@ class OrderList extends React.Component { </Link> </OrderItemStyles> ))} - </OrderUl> + </orderUl> </div> ); }} @@ -90,4 +78,3 @@ class OrderList extends React.Component { } export default OrderList; -export { USER_ORDERS_QUERY }; diff --git a/finished-application/frontend/components/Page.js b/finished-application/frontend/components/Page.js index c3e04ac..75ab84a 100644 --- a/finished-application/frontend/components/Page.js +++ b/finished-application/frontend/components/Page.js @@ -1,8 +1,7 @@ -import React from 'react'; +import React, { Component } from 'react'; import styled, { ThemeProvider, injectGlobal } from 'styled-components'; -import PropTypes from 'prop-types'; -import Header from './Header'; -import Meta from './Meta'; +import Header from '../components/Header'; +import Meta from '../components/Meta'; const theme = { red: '#FF0000', @@ -10,47 +9,49 @@ const theme = { grey: '#3A3A3A', lightgrey: '#E1E1E1', offWhite: '#EDEDED', - maxWidth: '1300px', + maxWidth: '1000px', bs: '0 12px 24px 0 rgba(0, 0, 0, 0.09)', }; +const StyledPage = styled.div` + background: white; + color: ${props => props.theme.black}; +`; + +const Inner = styled.div` + max-width: ${props => props.theme.maxWidth}; + margin: 0 auto; + padding: 2rem; +`; + injectGlobal` - html { - box-sizing: border-box; - font-size: 10px; + @font-face { + font-family: 'radnika_next'; + src: url('/static/radnikanext-medium-webfont.woff2') format('woff2'); + font-weight: normal; + font-style: normal; } - body { - font-family: 'radnika next', sans-serif; - padding: 0; - background-color: #ffffff; - margin: 0; - font-size: 1.5rem; - line-height: 2; + html { + box-sizing: border-box; + font-size: 10px; } *, *:before, *:after { box-sizing: inherit; } + body { + padding: 0; + margin: 0; + font-size: 1.5rem; + line-height: 2; + font-family: 'radnika_next'; + } a { - color: ${theme.black}; text-decoration: none; + color: ${theme.black}; } `; -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, - }; +class Page extends Component { render() { return ( <ThemeProvider theme={theme}> diff --git a/finished-application/frontend/components/Pagination.js b/finished-application/frontend/components/Pagination.js index f684840..b84af76 100644 --- a/finished-application/frontend/components/Pagination.js +++ b/finished-application/frontend/components/Pagination.js @@ -1,18 +1,14 @@ 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 { Query } from 'react-apollo'; import Head from 'next/head'; -import { perPage } from '../config'; +import Link from 'next/link'; import PaginationStyles from './styles/PaginationStyles'; - - +import { perPage } from '../config'; const PAGINATION_QUERY = gql` - query itemsConnection($skip: Int = 0, $first: Int = 4) { - itemsConnection(orderBy: createdAt_DESC, first: $first, skip: $skip) { + query PAGINATION_QUERY { + itemsConnection { aggregate { count } @@ -23,10 +19,10 @@ const PAGINATION_QUERY = gql` 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); + if (loading) return <p>Loading...</p>; + const count = data.itemsConnection.aggregate.count; + const pages = Math.ceil(count / perPage); + const page = props.page; return ( <PaginationStyles data-test="pagination"> <Head> @@ -42,15 +38,14 @@ const Pagination = props => ( }} > <a className="prev" aria-disabled={page <= 1}> - ←Prev + ← Prev </a> </Link> <p> - Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong> - </p> - <p> - <strong>{aggregate.count}</strong> Items Total + Page {props.page} of + <span className="totalPages">{pages}</span>! </p> + <p>{count} Items Total</p> <Link prefetch href={{ @@ -68,9 +63,5 @@ const Pagination = props => ( </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 index 52a6acf..4b08c39 100644 --- a/finished-application/frontend/components/Permissions.js +++ b/finished-application/frontend/components/Permissions.js @@ -1,57 +1,84 @@ -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 gql from 'graphql-tag'; import Table from './styles/Table'; +import SickButton from './styles/SickButton'; +import PropTypes from 'prop-types'; -const ALL_USERS_QUERY = gql` - query { - users { +const possiblePermissions = [ + 'ADMIN', + 'USER', + 'ITEMCREATE', + 'ITEMUPDATE', + 'ITEMDELETE', + 'PERMISSIONUPDATE', +]; + +const UPDATE_PERMISSIONS_MUTATION = gql` + mutation updatePermissions($permissions: [Permission], $userId: ID!) { + updatePermissions(permissions: $permissions, userId: $userId) { id + permissions name email - permissions } } `; -const UPDATE_PERMISSIONS_MUTATION = gql` - mutation updatePermissions($permissions: [Permission], $userId: ID!) { - updatePermissions(permissions: $permissions, userId: $userId) { +const ALL_USERS_QUERY = gql` + query { + users { id - permissions name email + permissions } } `; -const possiblePermissions = [ - 'ADMIN', - 'USER', - 'ITEMCREATE', - 'ITEMUPDATE', - 'ITEMDELETE', - 'PERMISSIONUPDATE', -]; +const Permissions = props => ( + <Query query={ALL_USERS_QUERY}> + {({ data, loading, error }) => ( + <div> + <Error error={error} /> + <div> + <h2>Manage Permissions</h2> + <Table> + <thead> + <tr> + <th>Name</th> + <th>Email</th> + {possiblePermissions.map(permission => <th key={permission}>{permission}</th>)} + <th>👇🏻</th> + </tr> + </thead> + <tbody>{data.users.map(user => <UserPermissions user={user} key={user.id} />)}</tbody> + </Table> + </div> + </div> + )} + </Query> +); -class User extends React.Component { +class UserPermissions extends React.Component { static propTypes = { user: PropTypes.shape({ - permissions: PropTypes.array.isRequired, - id: PropTypes.string.isRequired, + name: PropTypes.string, + email: PropTypes.string, + id: PropTypes.string, + permissions: PropTypes.array, }).isRequired, }; state = { permissions: this.props.user.permissions, }; - handlePermissionsChange = e => { + handlePermissionChange = (e) => { const checkbox = e.target; + // take a copy of the current permissions let updatedPermissions = [...this.state.permissions]; + // figure out if we need to remove or add this permission if (checkbox.checked) { - // add it in + // add it in! updatedPermissions.push(checkbox.value); } else { updatedPermissions = updatedPermissions.filter(permission => permission !== checkbox.value); @@ -59,75 +86,46 @@ class User extends React.Component { this.setState({ permissions: updatedPermissions }); }; render() { - const { user } = this.props; + const user = this.props.user; return ( - <Mutation mutation={UPDATE_PERMISSIONS_MUTATION}> + <Mutation + mutation={UPDATE_PERMISSIONS_MUTATION} + variables={{ + permissions: this.state.permissions, + userId: this.props.user.id, + }} + > {(updatePermissions, { loading, error }) => ( - <tr key={user.id} className="user"> - <Error error={error} /> - <td>{user.name}</td> - <td>{user.email}</td> - {possiblePermissions.map(permission => ( + <> + {error && <tr><td colspan="8"><Error error={error} /></td></tr>} + < tr > + <td>{user.name}</td> + <td>{user.email}</td> + {possiblePermissions.map(permission => ( + <td key={permission}> + <label htmlFor={`${user.id}-permission-${permission}`}> + <input + id={`${user.id}-permission-${permission}`} + type="checkbox" + checked={this.state.permissions.includes(permission)} + value={permission} + onChange={this.handlePermissionChange} + /> + </label> + </td> + ))} <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> + <SickButton type="button" disabled={loading} onClick={updatePermissions}> + Updat{loading ? 'ing' : 'e'} + </SickButton> </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> - )} + </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 index 5646749..80cfdbf 100644 --- a/finished-application/frontend/components/PleaseSignIn.js +++ b/finished-application/frontend/components/PleaseSignIn.js @@ -1,5 +1,4 @@ import { Query } from 'react-apollo'; -import PropTypes from 'prop-types'; import { CURRENT_USER_QUERY } from './User'; import Signin from './Signin'; @@ -7,40 +6,17 @@ 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> + <p>Please Sign In before Continuing</p> <Signin /> </div> ); } - // check if they need permissions - if (props.allowedPermissions) { - // check if they 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 index d29cbf1..025dae5 100644 --- a/finished-application/frontend/components/RemoveFromCart.js +++ b/finished-application/frontend/components/RemoveFromCart.js @@ -1,4 +1,4 @@ -import { Component } from 'react'; +import React from 'react'; import { Mutation } from 'react-apollo'; import styled from 'styled-components'; import PropTypes from 'prop-types'; @@ -23,29 +23,43 @@ const BigButton = styled.button` } `; -class RemoveFromCart extends Component { +class RemoveFromCart extends React.Component { static propTypes = { id: PropTypes.string.isRequired, }; - + // This gets called as soon as we get a response back from the server after a mutation has been performed update = (cache, payload) => { + // 1. first read the cache const data = cache.readQuery({ query: CURRENT_USER_QUERY }); - // console.log(data.me.cart[0]); + // 2. remove that item from the cart const cartItemId = payload.data.removeFromCart.id; data.me.cart = data.me.cart.filter(cartItem => cartItem.id !== cartItemId); + // 3. write it back to the cache cache.writeQuery({ query: CURRENT_USER_QUERY, data }); }; - render() { return ( <Mutation mutation={REMOVE_FROM_CART_MUTATION} variables={{ id: this.props.id }} update={this.update} + optimisticResponse={{ + __typename: 'Mutation', + removeFromCart: { + __typename: 'CartItem', + id: this.props.id, + }, + }} > - {(removeFromCart, { loading }) => ( - <BigButton disabled={loading} title="Remove From Cart" onClick={() => removeFromCart}> - × + {(removeFromCart, { loading, error }) => ( + <BigButton + disabled={loading} + onClick={() => { + removeFromCart().catch(err => alert(err.message)); + }} + title="Delete Item" + > + × </BigButton> )} </Mutation> diff --git a/finished-application/frontend/components/ResetRequest.js b/finished-application/frontend/components/RequestReset.js index 6aedcc1..be21639 100644 --- a/finished-application/frontend/components/ResetRequest.js +++ b/finished-application/frontend/components/RequestReset.js @@ -1,44 +1,49 @@ -import React from 'react'; +import React, { Component } 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!) { + mutation REQUEST_RESET_MUTATION($email: String!) { requestReset(email: $email) { - id + message } } `; -class ResetRequest extends React.Component { +class RequestReset extends Component { state = { email: '', }; - + saveToState = e => { + this.setState({ [e.target.name]: e.target.value }); + }; render() { return ( <Mutation mutation={REQUEST_RESET_MUTATION} variables={this.state}> - {(resetMutation, { loading, error, called }) => ( + {(reset, { error, loading, called }) => ( <Form + method="post" + data-test="form" onSubmit={async e => { e.preventDefault(); - await resetMutation(); + await reset(); + this.setState({ email: '' }); }} - data-test="ResetRequest" > - <Error error={error} /> - {!error && called && !loading && <p>Success! Check Your Email!</p>} <fieldset disabled={loading} aria-busy={loading}> + <h2>Request a password reset</h2> + <Error error={error} /> + {!error && !loading && called && <p>Success! Check your email for a reset link!</p>} <label htmlFor="email"> Email <input - value={this.state.email} - onChange={e => this.setState({ email: e.target.value })} + type="email" name="email" - type="text" placeholder="email" + value={this.state.email} + onChange={this.saveToState} /> </label> @@ -51,5 +56,5 @@ class ResetRequest extends React.Component { } } -export default ResetRequest; +export default RequestReset; export { REQUEST_RESET_MUTATION }; diff --git a/finished-application/frontend/components/Reset.js b/finished-application/frontend/components/Reset.js index bb2ece0..a132f03 100644 --- a/finished-application/frontend/components/Reset.js +++ b/finished-application/frontend/components/Reset.js @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { Component } from 'react'; import { Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; import gql from 'graphql-tag'; +import PropTypes from 'prop-types'; import Form from './styles/Form'; import Error from './ErrorMessage'; import { CURRENT_USER_QUERY } from './User'; @@ -16,26 +16,17 @@ const RESET_MUTATION = gql` } `; -class Reset extends React.Component { +class Reset extends Component { static propTypes = { resetToken: PropTypes.string.isRequired, }; - state = { - confirmPassword: '', password: '', + confirmPassword: '', }; - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); - }; - - resetPassword = async (e, resetMutation) => { - e.preventDefault(); - await resetMutation(); + this.setState({ [e.target.name]: e.target.value }); }; - render() { return ( <Mutation @@ -47,32 +38,41 @@ class Reset extends React.Component { }} refetchQueries={[{ query: CURRENT_USER_QUERY }]} > - {(resetMutation, { error, loading }) => ( - <Form onSubmit={e => this.resetPassword(e, resetMutation)}> - <Error error={error} /> + {(reset, { error, loading, called }) => ( + <Form + method="post" + onSubmit={async e => { + e.preventDefault(); + await reset(); + this.setState({ password: '', confirmPassword: '' }); + }} + > <fieldset disabled={loading} aria-busy={loading}> + <h2>Reset Your Password</h2> + <Error error={error} /> <label htmlFor="password"> - password + Password <input + type="password" + name="password" + placeholder="password" value={this.state.password} onChange={this.saveToState} - name="password" - type="password" - id="password" /> </label> - <label htmlFor="confirm"> - Confirm: + + <label htmlFor="confirmPassword"> + Confirm Your Password <input + type="password" + name="confirmPassword" + placeholder="confirmPassword" value={this.state.confirmPassword} onChange={this.saveToState} - name="confirmPassword" - type="password" - id="confirmPassword" /> </label> - <button type="submit">Request Reset!</button> + <button type="submit">Reset Your Password!</button> </fieldset> </Form> )} @@ -82,4 +82,3 @@ class Reset extends React.Component { } export default Reset; -export { RESET_MUTATION }; diff --git a/finished-application/frontend/components/Search.js b/finished-application/frontend/components/Search.js index a972db2..c29f93e 100644 --- a/finished-application/frontend/components/Search.js +++ b/finished-application/frontend/components/Search.js @@ -1,10 +1,10 @@ import React from 'react'; -import Downshift from 'downshift'; +import Downshift, { resetIdCounter } from 'downshift'; import Router from 'next/router'; import { ApolloConsumer } from 'react-apollo'; import gql from 'graphql-tag'; -import styled, { keyframes } from 'styled-components'; import debounce from 'lodash.debounce'; +import { DropDown, DropDownItem, SearchStyles } from './styles/DropDown'; const SEARCH_ITEMS_QUERY = gql` query SEARCH_ITEMS_QUERY($searchTerm: String!) { @@ -15,6 +15,7 @@ const SEARCH_ITEMS_QUERY = gql` } } `; + function routeToItem(item) { Router.push({ pathname: '/item', @@ -24,83 +25,38 @@ function routeToItem(item) { }); } -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; - } - } -`; - class AutoComplete extends React.Component { state = { items: [], loading: false, }; - onChange = async (e, client) => { - if (!e.target.value) { - return this.setState({ items: [] }); - } + onChange = debounce(async (e, client) => { + console.log('Searching...'); + // turn loading on this.setState({ loading: true }); - this.search(client, e.target.value); - }; - - search = debounce(async (client, searchTerm) => { + // Manually query apollo client const res = await client.query({ query: SEARCH_ITEMS_QUERY, - variables: { searchTerm }, + variables: { searchTerm: e.target.value }, + }); + this.setState({ + items: res.data.items, + loading: false, }); - this.setState({ items: res.data.items, loading: false }); }, 350); - render() { + resetIdCounter(); return ( <SearchStyles> - <Downshift onChange={routeToItem} itemToString={i => (i === null ? '' : i.title)}> + <Downshift onChange={routeToItem} itemToString={item => (item === null ? '' : item.title)}> {({ getInputProps, getItemProps, isOpen, inputValue, highlightedIndex }) => ( <div> - {/* This is the searchInput */} <ApolloConsumer> {client => ( <input {...getInputProps({ - placeholder: 'Search For Item', + type: 'search', + placeholder: 'Search For An Item', id: 'search', className: this.state.loading ? 'loading' : '', onChange: e => { @@ -111,24 +67,20 @@ class AutoComplete extends React.Component { /> )} </ApolloConsumer> - {/* This is the Dropdown */} {isOpen && ( <DropDown> {this.state.items.map((item, index) => ( <DropDownItem {...getItemProps({ item })} key={item.id} - highlighted={highlightedIndex === index} + highlighted={index === highlightedIndex} > <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> - )} + !this.state.loading && <DropDownItem> Nothing Found {inputValue}</DropDownItem>} </DropDown> )} </div> @@ -140,4 +92,3 @@ class AutoComplete extends React.Component { } export default AutoComplete; -export { SEARCH_ITEMS_QUERY }; diff --git a/finished-application/frontend/components/Signin.js b/finished-application/frontend/components/Signin.js index 53cdb76..4fd3013 100644 --- a/finished-application/frontend/components/Signin.js +++ b/finished-application/frontend/components/Signin.js @@ -1,9 +1,9 @@ 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'; +import Error from './ErrorMessage'; +import { CURRENT_USER_QUERY } from './User'; const SIGNIN_MUTATION = gql` mutation SIGNIN_MUTATION($email: String!, $password: String!) { @@ -17,15 +17,13 @@ const SIGNIN_MUTATION = gql` class Signin extends Component { state = { - email: '', + name: '', password: '', + email: '', }; - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); + this.setState({ [e.target.name]: e.target.value }); }; - render() { return ( <Mutation @@ -33,33 +31,33 @@ class Signin extends Component { variables={this.state} refetchQueries={[{ query: CURRENT_USER_QUERY }]} > - {(signin, { loading, error }) => ( + {(signup, { error, loading }) => ( <Form - onSubmit={e => { + method="post" + onSubmit={async e => { e.preventDefault(); - signin(); + await signup(); + this.setState({ name: '', email: '', password: '' }); }} > - <Error error={error} /> <fieldset disabled={loading} aria-busy={loading}> + <h2>Sign into your account</h2> + <Error error={error} /> <label htmlFor="email"> Email <input - value={this.state.email} - onChange={this.saveToState} + type="email" name="email" - type="text" placeholder="email" + value={this.state.email} + onChange={this.saveToState} /> </label> - <label htmlFor="password"> Password <input type="password" name="password" - id="password" - className="password" placeholder="password" value={this.state.password} onChange={this.saveToState} @@ -76,4 +74,3 @@ class Signin extends Component { } export default Signin; -export { SIGNIN_MUTATION }; diff --git a/finished-application/frontend/components/Signout.js b/finished-application/frontend/components/Signout.js index ae87631..f852531 100644 --- a/finished-application/frontend/components/Signout.js +++ b/finished-application/frontend/components/Signout.js @@ -11,15 +11,9 @@ const SIGN_OUT_MUTATION = gql` } `; -class Signout extends Component { - render() { - return ( - <Mutation mutation={SIGN_OUT_MUTATION} refetchQueries={[{ query: CURRENT_USER_QUERY }]}> - {signout => <button onClick={signout}>Sign Out</button>} - </Mutation> - ); - } -} - +const Signout = props => ( + <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 index 9164e67..e2f1a3f 100644 --- a/finished-application/frontend/components/Signup.js +++ b/finished-application/frontend/components/Signup.js @@ -1,9 +1,9 @@ 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'; +import { CURRENT_USER_QUERY } from './User'; const SIGNUP_MUTATION = gql` mutation SIGNUP_MUTATION($email: String!, $name: String!, $password: String!) { @@ -17,16 +17,13 @@ const SIGNUP_MUTATION = gql` class Signup extends Component { state = { - email: '', name: '', + email: '', password: '', }; - saveToState = e => { - const { name, value } = e.target; - this.setState({ [name]: value }); + this.setState({ [e.target.name]: e.target.value }); }; - render() { return ( <Mutation @@ -34,28 +31,28 @@ class Signup extends Component { variables={this.state} refetchQueries={[{ query: CURRENT_USER_QUERY }]} > - {(signup, { loading, error }) => ( + {(signup, { error, loading }) => ( <Form method="post" onSubmit={async e => { e.preventDefault(); - const res = await signup(); + await signup(); + this.setState({ name: '', email: '', password: '' }); }} > <fieldset disabled={loading} aria-busy={loading}> + <h2>Sign Up for An Account</h2> <Error error={error} /> - <h2>Sign Up for an Account</h2> <label htmlFor="email"> Email <input - value={this.state.email} - onChange={this.saveToState} + type="email" name="email" - type="text" placeholder="email" + value={this.state.email} + onChange={this.saveToState} /> </label> - <label htmlFor="name"> Name <input @@ -66,21 +63,18 @@ class Signup extends Component { onChange={this.saveToState} /> </label> - - <label htmlFor="signupPassword"> + <label htmlFor="password"> 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> + <button type="submit">Sign Up!</button> </fieldset> </Form> )} diff --git a/finished-application/frontend/components/SingleItem.js b/finished-application/frontend/components/SingleItem.js index 83ef8a0..145b5ae 100644 --- a/finished-application/frontend/components/SingleItem.js +++ b/finished-application/frontend/components/SingleItem.js @@ -1,23 +1,9 @@ +import React, { Component } from 'react'; +import gql from 'graphql-tag'; import { Query } from 'react-apollo'; -import PropTypes from 'prop-types'; +import Error from './ErrorMessage'; 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; @@ -38,40 +24,47 @@ const SingleItemStyles = styled.div` } `; -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, -}; +const SINGLE_ITEM_QUERY = gql` + query SINGLE_ITEM_QUERY($id: ID!) { + item(where: { id: $id }) { + id + title + description + largeImage + } + } +`; +class SingleItem extends Component { + render() { + return ( + <Query + query={SINGLE_ITEM_QUERY} + variables={{ + id: this.props.id, + }} + > + {({ error, loading, data }) => { + if (error) return <Error error={error} />; + if (loading) return <p>Loading...</p>; + if (!data.item) return <p>No Item Found for {this.props.id}</p>; + const item = data.item; + return ( + <SingleItemStyles> + <Head> + <title>Sick Fits | {item.title}</title> + </Head> + <img src={item.largeImage} alt={item.title} /> + <div className="details"> + <h2>Viewing {item.title}</h2> + <p>{item.description}</p> + </div> + </SingleItemStyles> + ); + }} + </Query> + ); + } +} export default SingleItem; export { SINGLE_ITEM_QUERY }; diff --git a/finished-application/frontend/components/TakeMyMoney.js b/finished-application/frontend/components/TakeMyMoney.js index b9e42ea..ca87832 100644 --- a/finished-application/frontend/components/TakeMyMoney.js +++ b/finished-application/frontend/components/TakeMyMoney.js @@ -1,6 +1,6 @@ -import { Component } from 'react'; +import React from 'react'; import StripeCheckout from 'react-stripe-checkout'; -import { Mutation, Query } from 'react-apollo'; +import { Mutation } from 'react-apollo'; import Router from 'next/router'; import NProgress from 'nprogress'; import PropTypes from 'prop-types'; @@ -27,28 +27,27 @@ function totalItems(cart) { return cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); } -class TakeMyMoney extends Component { +class TakeMyMoney extends React.Component { onToken = async (res, createOrder) => { NProgress.start(); + // manually call the mutation once we have the stripe token const order = await createOrder({ variables: { token: res.id, }, + }).catch(err => { + alert(err.message); }); - console.log(order); - // Route them to that order page - const { id } = order.data.createOrder; Router.push({ - pathname: `/order`, - query: { id }, + pathname: '/order', + query: { id: order.data.createOrder.id }, }); }; render() { return ( <User> - {({ data: { me }, error }) => { - if (!me || !me.cart.length) return null; - if (error) return <Error error={error} />; + {({ data: { me }, loading }) => { + if (loading) return null; return ( <Mutation mutation={CREATE_ORDER_MUTATION} @@ -57,13 +56,13 @@ class TakeMyMoney extends Component { {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" + name="Sick Fits" + description={`Order of ${totalItems(me.cart)} items!`} + image={me.cart.length && me.cart[0].item && me.cart[0].item.image} + stripeKey="pk_test_Vtknn6vSdcZWSG2JWvEiWSqC" currency="USD" email={me.email} + token={res => this.onToken(res, createOrder)} > {this.props.children} </StripeCheckout> @@ -76,8 +75,5 @@ class TakeMyMoney extends Component { } } -TakeMyMoney.propTypes = { - children: PropTypes.any, -}; - export default TakeMyMoney; +export { CREATE_ORDER_MUTATION }; diff --git a/finished-application/frontend/components/UpdateItem.js b/finished-application/frontend/components/UpdateItem.js index 08ba254..11aef32 100644 --- a/finished-application/frontend/components/UpdateItem.js +++ b/finished-application/frontend/components/UpdateItem.js @@ -1,82 +1,79 @@ import React, { Component } from 'react'; -import { Query, Mutation } from 'react-apollo'; -import PropTypes from 'prop-types'; +import { Mutation, Query } from 'react-apollo'; import gql from 'graphql-tag'; -import { SINGLE_ITEM_QUERY } from './SingleItem'; +import Router from 'next/router'; import Form from './styles/Form'; +import formatMoney from '../lib/formatMoney'; import Error from './ErrorMessage'; +const SINGLE_ITEM_QUERY = gql` + query SINGLE_ITEM_QUERY($id: ID!) { + item(where: { id: $id }) { + id + title + description + price + } + } +`; const UPDATE_ITEM_MUTATION = gql` - mutation updateItem($id: ID!, $title: String, $description: String, $price: Int) { - updateItem(id: $id, description: $description, title: $title, price: $price) { + mutation UPDATE_ITEM_MUTATION($id: ID!, $title: String, $description: String, $price: Int) { + updateItem(id: $id, title: $title, description: $description, price: $price) { id + title + description + price } } `; 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 }); + state = {}; + handleChange = e => { + const { name, type, value } = e.target; + const val = type === 'number' ? parseFloat(value) : value; + this.setState({ [name]: val }); }; - updateItem = async (e, updateItemMutation) => { - console.log('Updating Item'); e.preventDefault(); - + console.log('Updating Item!!'); + console.log(this.state); const res = await updateItemMutation({ - // pass in those variables from state variables: { id: this.props.id, - ...this.state.item, + ...this.state, }, }); - console.log(res); + console.log('Updated!!'); }; render() { return ( - <Query query={SINGLE_ITEM_QUERY} variables={{ id: this.props.id }}> - {({ data: { items }, loading }) => { + <Query + query={SINGLE_ITEM_QUERY} + variables={{ + id: this.props.id, + }} + > + {({ data, loading }) => { if (loading) return <p>Loading...</p>; - if (!items || !items.length) return <p>Item Not Found</p>; - const [item] = items; + if (!data.item) return <p>No Item Found for ID {this.props.id}</p>; return ( - <Mutation mutation={UPDATE_ITEM_MUTATION}> - {(updateItemMutation, { error }) => ( - <Form onSubmit={e => this.updateItem(e, updateItemMutation)}> + <Mutation mutation={UPDATE_ITEM_MUTATION} variables={this.state}> + {(updateItem, { loading, error }) => ( + <Form onSubmit={e => this.updateItem(e, updateItem)}> <Error error={error} /> - <h2>Edit {item.title}</h2> <fieldset disabled={loading} aria-busy={loading}> <label htmlFor="title"> Title <input + type="text" 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} + placeholder="Title" + required + defaultValue={data.item.title} + onChange={this.handleChange} /> </label> @@ -84,12 +81,27 @@ class UpdateItem extends Component { Price <input type="number" + id="price" name="price" - onChange={this.saveToState} - defaultValue={item.price} + placeholder="Price" + required + defaultValue={data.item.price} + onChange={this.handleChange} + /> + </label> + + <label htmlFor="description"> + Description + <textarea + id="description" + name="description" + placeholder="Enter A Description" + required + defaultValue={data.item.description} + onChange={this.handleChange} /> </label> - <button type="submit">Save...</button> + <button type="submit">Sav{loading ? 'ing' : 'e'} Changes</button> </fieldset> </Form> )} diff --git a/finished-application/frontend/components/User.js b/finished-application/frontend/components/User.js index 19c568d..6f8a4fb 100644 --- a/finished-application/frontend/components/User.js +++ b/finished-application/frontend/components/User.js @@ -11,20 +11,16 @@ const CURRENT_USER_QUERY = gql` permissions orders { id - charge - total } cart { id quantity item { - __typename id - title price - description image - largeImage + title + description } } } @@ -33,7 +29,7 @@ const CURRENT_USER_QUERY = gql` const User = props => ( <Query {...props} query={CURRENT_USER_QUERY}> - {result => props.children(result)} + {payload => props.children(payload)} </Query> ); diff --git a/finished-application/frontend/components/styles/DropDown.js b/finished-application/frontend/components/styles/DropDown.js new file mode 100644 index 0000000..df86aee --- /dev/null +++ b/finished-application/frontend/components/styles/DropDown.js @@ -0,0 +1,47 @@ +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/NavStyles.js b/finished-application/frontend/components/styles/NavStyles.js index 41523df..fe4abda 100644 --- a/finished-application/frontend/components/styles/NavStyles.js +++ b/finished-application/frontend/components/styles/NavStyles.js @@ -18,6 +18,8 @@ const NavStyles = styled.ul` background: none; border: 0; cursor: pointer; + color: ${props => props.theme.black}; + font-weight: 800; @media (max-width: 700px) { font-size: 10px; padding: 0 10px; diff --git a/finished-application/frontend/components/styles/OrderStyles.js b/finished-application/frontend/components/styles/OrderStyles.js index 4a2d804..f461b70 100644 --- a/finished-application/frontend/components/styles/OrderStyles.js +++ b/finished-application/frontend/components/styles/OrderStyles.js @@ -30,7 +30,6 @@ const OrderStyles = styled.div` padding-bottom: 2rem; img { width: 100%; - height: 100%; object-fit: cover; } } diff --git a/finished-application/frontend/components/styles/Table.js b/finished-application/frontend/components/styles/Table.js index e9d0673..b2cd6c4 100644 --- a/finished-application/frontend/components/styles/Table.js +++ b/finished-application/frontend/components/styles/Table.js @@ -11,7 +11,7 @@ const Table = styled.table` th { border-bottom: 1px solid ${props => props.theme.offWhite}; border-right: 1px solid ${props => props.theme.offWhite}; - padding: 10px 5px; + padding: 5px; position: relative; &:last-child { border-right: none; @@ -20,6 +20,10 @@ const Table = styled.table` width: 100%; } } + label { + padding: 10px 5px; + display: block; + } } tr { &:hover { |
