summaryrefslogtreecommitdiffstats
path: root/finished-application/frontend/components
diff options
context:
space:
mode:
authorRandy Ridge <randyridge@gmail.com>2018-09-14 20:01:25 -0400
committerGitHub <noreply@github.com>2018-09-14 20:01:25 -0400
commit908180c77cd38011eeedcae949613da2a3391e5e (patch)
treeb59bf1aae819a04d453cde72f6e601f5561a740f /finished-application/frontend/components
parent1f6667d233a4a2e9df977204d83a8f749c050c75 (diff)
parentb3bebda57ba187b7fa10398054b54c05d3fd3555 (diff)
Merge branch 'master' into randyridge/our
Diffstat (limited to 'finished-application/frontend/components')
-rw-r--r--finished-application/frontend/components/AddToCart.js62
-rw-r--r--finished-application/frontend/components/Cart.js52
-rw-r--r--finished-application/frontend/components/CartCount.js42
-rw-r--r--finished-application/frontend/components/CartItem.js16
-rw-r--r--finished-application/frontend/components/CreateItem.js96
-rw-r--r--finished-application/frontend/components/DeleteItem.js55
-rw-r--r--finished-application/frontend/components/Dump.js19
-rw-r--r--finished-application/frontend/components/EditUser.js81
-rw-r--r--finished-application/frontend/components/Header.js56
-rw-r--r--finished-application/frontend/components/Item.js23
-rw-r--r--finished-application/frontend/components/Items.js49
-rw-r--r--finished-application/frontend/components/LoadingItem.js22
-rw-r--r--finished-application/frontend/components/Meta.js17
-rw-r--r--finished-application/frontend/components/Nav.js82
-rw-r--r--finished-application/frontend/components/Order.js30
-rw-r--r--finished-application/frontend/components/OrderList.js39
-rw-r--r--finished-application/frontend/components/Page.js63
-rw-r--r--finished-application/frontend/components/Pagination.js35
-rw-r--r--finished-application/frontend/components/Permissions.js172
-rw-r--r--finished-application/frontend/components/PleaseSignIn.js26
-rw-r--r--finished-application/frontend/components/RemoveFromCart.js30
-rw-r--r--finished-application/frontend/components/RequestReset.js (renamed from finished-application/frontend/components/ResetRequest.js)33
-rw-r--r--finished-application/frontend/components/Reset.js55
-rw-r--r--finished-application/frontend/components/Search.js85
-rw-r--r--finished-application/frontend/components/Signin.js33
-rw-r--r--finished-application/frontend/components/Signout.js16
-rw-r--r--finished-application/frontend/components/Signup.js30
-rw-r--r--finished-application/frontend/components/SingleItem.js95
-rw-r--r--finished-application/frontend/components/TakeMyMoney.js36
-rw-r--r--finished-application/frontend/components/UpdateItem.js110
-rw-r--r--finished-application/frontend/components/User.js10
-rw-r--r--finished-application/frontend/components/styles/DropDown.js47
-rw-r--r--finished-application/frontend/components/styles/NavStyles.js2
-rw-r--r--finished-application/frontend/components/styles/OrderStyles.js1
-rw-r--r--finished-application/frontend/components/styles/Table.js6
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">
&times;
</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} &times; {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&nbsp;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"
+ >
+ &times;
</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 {