summaryrefslogtreecommitdiffstats
path: root/frontend/components
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-03-22 16:29:31 -0400
committerWes Bos <wesbos@gmail.com>2018-03-22 16:29:31 -0400
commita6af043686e4375d7944e91cae3d5b63c59edab0 (patch)
treef814d412b14a2a47f56c933ce38f458055f2f83b /frontend/components
parent93495ce146c4e81ecfab3a0bca99460e75a759da (diff)
Migrate to render props
Diffstat (limited to 'frontend/components')
-rw-r--r--frontend/components/AddToCart.js44
-rw-r--r--frontend/components/Cart.js76
-rw-r--r--frontend/components/CartCount.js1
-rw-r--r--frontend/components/CreateItem.js164
-rw-r--r--frontend/components/DeleteItem.js39
-rw-r--r--frontend/components/EditUser.js69
-rw-r--r--frontend/components/ErrorMessage.js27
-rw-r--r--frontend/components/Header.js7
-rw-r--r--frontend/components/Item.js16
-rw-r--r--frontend/components/Items.js59
-rw-r--r--frontend/components/Nav.js87
-rw-r--r--frontend/components/Order.js95
-rw-r--r--frontend/components/OrderList.js84
-rw-r--r--frontend/components/Page.js3
-rw-r--r--frontend/components/Pagination.js94
-rw-r--r--frontend/components/RemoveFromCart.js29
-rw-r--r--frontend/components/Reset.js115
-rw-r--r--frontend/components/ResetRequest.js69
-rw-r--r--frontend/components/Search.js38
-rw-r--r--frontend/components/Signin.js88
-rw-r--r--frontend/components/Signout.js17
-rw-r--r--frontend/components/Signup.js118
-rw-r--r--frontend/components/SingleItem.js4
-rw-r--r--frontend/components/TakeMyMoney.js63
-rw-r--r--frontend/components/UpdateItem.js4
-rw-r--r--frontend/components/styles/Form.js35
26 files changed, 726 insertions, 719 deletions
diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js
index 22cfe02..4757296 100644
--- a/frontend/components/AddToCart.js
+++ b/frontend/components/AddToCart.js
@@ -1,36 +1,34 @@
import { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
-import styled from 'styled-components';
+import { Mutation } from 'react-apollo';
import PropTypes from 'prop-types';
-import { removeFromCartEnhancer, userEnhancer, addToCartEnhancer, singleItemEnhancer } from '../enhancers/enhancers';
+import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY } from '../queries/index';
class AddToCart extends Component {
static propTypes = {
- currentUser: PropTypes.object.isRequired,
+ id: PropTypes.string.isRequired,
};
- componentDidMount() {
- this.props.currentUser.refetch();
- }
-
- handleAddToCart = async () => {
- const res = await this.props.addToCart({
- variables: {
- id: this.props.id,
- },
- });
+ update = (proxy, payload) => {
+ const newCartItem = payload.data.addToCart;
+ const data = proxy.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];
+ }
+ proxy.writeQuery({ query: CURRENT_USER_QUERY, data });
};
render() {
- return <button onClick={this.handleAddToCart}>๐Ÿ›’ Add To Cart</button>;
+ const { id } = this.props;
+ return (
+ <Mutation mutation={ADD_TO_CART_MUTATION} variables={{ id }} update={this.update}>
+ {addToCart => <button onClick={addToCart}>๐Ÿ›’ Add To Cart</button>}
+ </Mutation>
+ );
}
}
-AddToCart.propTypes = {
- currentUser: PropTypes.object.isRequired,
- addToCart: PropTypes.func.isRequired,
- id: PropTypes.string.isRequired,
-};
-
-export default compose(userEnhancer, addToCartEnhancer)(AddToCart);
-export { AddToCart };
+export default AddToCart;
diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js
index 26a11d4..fee757e 100644
--- a/frontend/components/Cart.js
+++ b/frontend/components/Cart.js
@@ -1,12 +1,11 @@
import React from 'react';
-import { compose } from 'react-apollo';
+import { Query } from 'react-apollo';
import styled from 'styled-components';
-import PropTypes from 'prop-types';
-import { userEnhancer } from '../enhancers/enhancers';
import TakeMyMoney from './TakeMyMoney';
import formatMoney from '../lib/formatMoney';
import { UIContext } from './UIContext';
import CartItem from './CartItem';
+import { CURRENT_USER_QUERY } from '../queries/index';
const CartStyles = styled.div`
padding: 20px;
@@ -73,43 +72,42 @@ const CloseButton = styled.button`
right: 0;
`;
-const Cart = props => {
- const { me } = props.currentUser;
- if (!me) return null;
- const itemCount = me.cart.length;
- const totalPrice = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity * cartItem.item.price, 0);
+function calcTotalPrice(cart) {
+ return cart.reduce((tally, cartItem) => tally + cartItem.quantity * cartItem.item.price, 0);
+}
- return (
- <UIContext.Consumer>
- {context => (
- <CartStyles open={context.state.isCartOpen}>
- <header>
- <CloseButton title="close" onClick={context.toggle}>
- &times;
- </CloseButton>
+const Cart = props => (
+ <UIContext.Consumer>
+ {context => (
+ <Query query={CURRENT_USER_QUERY}>
+ {({ data: { me }, error, loading }) => {
+ if (loading || error || !me) return null;
+ return (
+ <CartStyles open={context.state.isCartOpen}>
+ <header>
+ <CloseButton title="close" onClick={context.toggle}>
+ &times;
+ </CloseButton>
- <Supreme>{me.name}'s Cart.</Supreme>
- <p>
- You have {itemCount} item{itemCount === 1 ? '' : 's'} in your cart.
- </p>
- </header>
+ <Supreme>{me.name}'s Cart.</Supreme>
+ <p>
+ You have {me.cart.length} item{me.cart.length === 1 ? '' : 's'} in your cart.
+ </p>
+ </header>
- <CartUl>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</CartUl>
- <footer>
- <p>{formatMoney(totalPrice)}</p>
- <TakeMyMoney>
- <button>Checkout</button>
- </TakeMyMoney>
- </footer>
- </CartStyles>
- )}
- </UIContext.Consumer>
- );
-};
+ <CartUl>{me.cart.map(cartItem => <CartItem key={cartItem.id} cartItem={cartItem} />)}</CartUl>
+ <footer>
+ <p>{formatMoney(calcTotalPrice(me.cart))}</p>
+ <TakeMyMoney>
+ <button>Checkout</button>
+ </TakeMyMoney>
+ </footer>
+ </CartStyles>
+ );
+ }}
+ </Query>
+ )}
+ </UIContext.Consumer>
+);
-Cart.propTypes = {
- currentUser: PropTypes.object.isRequired,
-};
-
-export default compose(userEnhancer)(Cart);
-export { Cart };
+export default Cart;
diff --git a/frontend/components/CartCount.js b/frontend/components/CartCount.js
index 7949307..245b07b 100644
--- a/frontend/components/CartCount.js
+++ b/frontend/components/CartCount.js
@@ -50,4 +50,3 @@ const CartCount = ({ count }) => (
);
export default CartCount;
-export { CartCount };
diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js
index 74a6cee..d166c81 100644
--- a/frontend/components/CreateItem.js
+++ b/frontend/components/CreateItem.js
@@ -1,36 +1,24 @@
import React, { Component } from 'react';
-import { graphql, gql } from 'react-apollo';
+import { Mutation } from 'react-apollo';
import { CREATE_ITEM_MUTATION } from '../queries';
-import ErrorMessage from './ErrorMessage';
+import Error from './ErrorMessage';
import Form from './styles/Form';
import PropTypes from 'prop-types';
+import Router from 'next/router';
class CreateItem extends Component {
- static propTypes = {
- createItemMutation: PropTypes.func.isRequired,
- };
-
state = {
- item: {
- title: '',
- description: '',
- image: '',
- largeImage: '',
- price: 0,
- },
- loading: false,
- error: {
- message: null,
- },
+ title: '',
+ description: '',
+ image: '',
+ largeImage: '',
+ price: 0,
};
handleChange = e => {
const { name, value, type } = e.target;
- const updatedItem = {
- ...this.state.item,
- [name]: type === 'number' ? parseFloat(value) : value,
- };
- this.setState({ item: updatedItem });
+ const val = type === 'number' ? parseFloat(value) : value;
+ this.setState({ [name]: val });
};
uploadFile = async e => {
@@ -46,81 +34,73 @@ class CreateItem extends Component {
body: data,
});
const file = await res.json();
- this.setState({ image: file.secure_url, largeImage: file.eager[0].secure_url, loading: false });
- };
-
- createItem = async e => {
- e.preventDefault();
- // TODO: handle any errors
- // turn loading on
- this.setState({ loading: true });
- try {
- const res = await this.props.createItemMutation({
- // pass in those variables from state
- variables: {
- ...this.state.item,
- },
- });
- } catch (error) {
- this.setState({ error });
- }
- this.setState({ loading: false });
+ this.setState({
+ image: file.secure_url,
+ largeImage: file.eager[0].secure_url,
+ loading: false,
+ });
};
render() {
return (
- <div>
- <Form onSubmit={this.createItem}>
- {this.state.loading ? 'LOADING...' : null}
- <ErrorMessage error={this.state.error} onButtonClick={() => this.setState({ error: {} })} />
- <p>
- Image
- <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" />
- {this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null}
- </p>
- <p>
- Title
- <input
- value={this.state.title}
- onChange={this.handleChange}
- type="text"
- name="title"
- id="title"
- placeholder="Title"
- />
- </p>
- <label>
- Price
- <input
- type="number"
- id="price"
- name="price"
- min="0"
- value={this.state.price}
- onChange={this.handleChange}
- />
- </label>
- <textarea
- id="description"
- name="description"
- value={this.state.description}
- onChange={this.handleChange}
- placeholder="The desc for this item"
- />
- <button disabled={this.state.loading} type="submit">
- Submit
- </button>
- </Form>
- </div>
+ <Mutation mutation={CREATE_ITEM_MUTATION} variables={this.state}>
+ {(createItem, { loading, error }) => (
+ <Form
+ onSubmit={async e => {
+ e.preventDefault();
+ const { data: { createItem: item } } = await createItem();
+ Router.push({
+ pathname: `/item`,
+ query: { id: item.id },
+ });
+ }}
+ >
+ <h2>Sell an Item.</h2>
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <label>
+ Image
+ <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" />
+ {this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null}
+ </label>
+ <label>
+ Title
+ <input
+ value={this.state.title}
+ onChange={this.handleChange}
+ type="text"
+ name="title"
+ id="title"
+ placeholder="Title"
+ />
+ </label>
+ <label>
+ Price
+ <input
+ type="number"
+ id="price"
+ name="price"
+ min="0"
+ value={this.state.price}
+ onChange={this.handleChange}
+ />
+ </label>
+ <textarea
+ id="description"
+ name="description"
+ value={this.state.description}
+ onChange={this.handleChange}
+ placeholder="The desc for this item"
+ />
+ <button disabled={this.state.loading} type="submit">
+ Submit
+ </button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
);
}
}
-export default graphql(CREATE_ITEM_MUTATION, {
- name: 'createItemMutation',
- options: {
- refetchQueries: ['AllItemsQuery'],
- },
-})(CreateItem);
-
-export { CreateItem };
+export default CreateItem;
diff --git a/frontend/components/DeleteItem.js b/frontend/components/DeleteItem.js
new file mode 100644
index 0000000..30b2bf7
--- /dev/null
+++ b/frontend/components/DeleteItem.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import { Mutation } from 'react-apollo';
+import PropTypes from 'prop-types';
+import { REMOVE_ITEM_MUTATION, ALL_ITEMS_QUERY } from '../queries/index';
+
+class DeleteItem extends React.Component {
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ };
+
+ update = (proxy, payload) => {
+ const deletedItem = payload.data.deleteItem;
+ const data = proxy.readQuery({ query: ALL_ITEMS_QUERY });
+ // filter this one out
+ data.items = data.items.filter(item => item.id !== deletedItem.id);
+ // write the data back to the proxy
+ proxy.writeQuery({ query: ALL_ITEMS_QUERY, data });
+ };
+
+ render() {
+ return (
+ <Mutation mutation={REMOVE_ITEM_MUTATION} variables={{ id: this.props.id }} update={this.update}>
+ {removeItem => (
+ <button
+ onClick={() => {
+ if (confirm('Are you sure you want to delete this item?')) {
+ removeItem();
+ }
+ }}
+ >
+ &times; Delete item
+ </button>
+ )}
+ </Mutation>
+ );
+ }
+}
+
+export default DeleteItem;
diff --git a/frontend/components/EditUser.js b/frontend/components/EditUser.js
index b885fe6..a7a8f1c 100644
--- a/frontend/components/EditUser.js
+++ b/frontend/components/EditUser.js
@@ -1,59 +1,64 @@
import React from 'react';
-import { compose } from 'react-apollo';
-import PropTypes from 'prop-types';
+import { Query, Mutation } from 'react-apollo';
import Form from './styles/Form';
-import { userEnhancer, updateUserEnhancer } from '../enhancers/enhancers';
+import { CURRENT_USER_QUERY, UPDATE_USER_MUTATION } from '../queries/index';
+import Error from './ErrorMessage';
class EditUser extends React.Component {
- static propTypes = {
- currentUser: PropTypes.object.isRequired,
- updateUser: PropTypes.func.isRequired,
- };
-
state = {
changes: {},
};
handleChange = e => {
const { name, value } = e.target;
- const { me } = this.props.currentUser;
const changes = {
...this.state.changes,
[name]: value,
};
- if (value === me[e.target.name]) {
- delete changes[name];
- }
this.setState({ changes });
};
- handleSubmit = async e => {
+ handleSubmit = async (e, updateUser) => {
e.preventDefault();
- await this.props.updateUser({
- variables: this.state.changes,
- });
- this.props.currentUser.refetch();
+ // only submit if there are real changes
+ if (Object.keys(this.state.changes).length === 0) return;
+ await updateUser();
this.setState({ changes: {} });
};
render() {
- const { me } = this.props.currentUser;
- if (!me) return <p>You must be logged in</p>;
return (
- <Form onSubmit={this.handleSubmit}>
- <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>
- </Form>
+ <Query query={CURRENT_USER_QUERY}>
+ {({ data: { me } }) => {
+ if (!me) return <p>You must be logged in</p>;
+ return (
+ <Mutation
+ mutation={UPDATE_USER_MUTATION}
+ refetchQueries={[{ query: CURRENT_USER_QUERY }]}
+ variables={this.state.changes}
+ >
+ {(updateUser, { loading, error, called, data }) => (
+ <Form onSubmit={e => this.handleSubmit(e, updateUser)}>
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <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>
+ );
+ }}
+ </Query>
);
}
}
-export default compose(userEnhancer, updateUserEnhancer)(EditUser);
-export { EditUser };
+export default EditUser;
diff --git a/frontend/components/ErrorMessage.js b/frontend/components/ErrorMessage.js
index 12de51d..ed5d031 100644
--- a/frontend/components/ErrorMessage.js
+++ b/frontend/components/ErrorMessage.js
@@ -4,29 +4,34 @@ import React from 'react';
import PropTypes from 'prop-types';
const StyledError = styled.div`
- background: pink;
- border: 2px solid red;
- padding: 20px;
- display: flex;
+ padding: 2rem;
+ background: white;
+ margin: 2rem 0;
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ border-left: 5px solid red;
p {
margin: 0;
- flex: 1 0 auto;
+ font-weight: 100;
+ }
+ strong {
+ margin-right: 1rem;
}
`;
-const DisplayError = props => {
- if (!props.error || !props.error.message) return null;
+const DisplayError = ({ error }) => {
+ if (!error || !error.message) return null;
return (
<StyledError>
- <p>{props.error.message}</p>
- <button onClick={props.onButtonClick}>&times;</button>
+ <p>
+ <strong>Shoot!</strong>
+ {error.message}
+ </p>
</StyledError>
);
};
DisplayError.propTypes = {
- error: PropTypes.object.isRequired,
- onButtonClick: PropTypes.func.isRequired,
+ error: PropTypes.object,
};
export default DisplayError;
diff --git a/frontend/components/Header.js b/frontend/components/Header.js
index 0b815f9..053a18b 100644
--- a/frontend/components/Header.js
+++ b/frontend/components/Header.js
@@ -1,6 +1,4 @@
-import { Component } from 'react';
-import { compose } from 'react-apollo';
-import { userEnhancer } from '../enhancers/enhancers';
+import React from 'react';
import NProgress from 'nprogress';
import Router from 'next/router';
import styled from 'styled-components';
@@ -60,5 +58,4 @@ const Header = props => (
</StyledHeader>
);
-export default compose(userEnhancer)(Header);
-// export default Header;
+export default Header;
diff --git a/frontend/components/Item.js b/frontend/components/Item.js
index 33b3951..9bff385 100644
--- a/frontend/components/Item.js
+++ b/frontend/components/Item.js
@@ -1,11 +1,11 @@
import React from 'react';
import styled from 'styled-components';
-import { compose } from 'react-apollo';
import Link from 'next/link';
import Title from './styles/Title';
import AddToCart from './AddToCart';
+import DeleteItem from './DeleteItem';
import formatMoney from '../lib/formatMoney';
-import { removeItemMutation } from '../enhancers/enhancers';
+import PropTypes from 'prop-types';
const Item = styled.div`
background: white;
@@ -57,11 +57,10 @@ const PriceTag = styled.span`
`;
class ItemComponent extends React.Component {
- removeItem = () => {
- if (confirm('Are you sure you want to delete this item?')) {
- this.props.removeItem({ variables: { id: this.props.item.id } });
- }
+ static propTypes = {
+ item: PropTypes.object.isRequired,
};
+
render() {
const item = this.props.item;
return (
@@ -92,12 +91,11 @@ class ItemComponent extends React.Component {
<a>Edit โœ๏ธ</a>
</Link>
<AddToCart id={item.id} />
- <button onClick={this.removeItem}>&times; Delete item</button>
+ <DeleteItem id={item.id} />
</div>
</Item>
);
}
}
-export { ItemComponent };
-export default compose(removeItemMutation)(ItemComponent);
+export default ItemComponent;
diff --git a/frontend/components/Items.js b/frontend/components/Items.js
index 1b0c8bb..1f4918f 100644
--- a/frontend/components/Items.js
+++ b/frontend/components/Items.js
@@ -1,9 +1,20 @@
import React from 'react';
-import { compose } from 'react-apollo';
+import { Query } from 'react-apollo';
import styled from 'styled-components';
+import PropTypes from 'prop-types';
import Pagination from './Pagination';
import Item from './Item';
-import { itemEnhancer } from '../enhancers/enhancers';
+import { perPage } from '../config';
+import { ALL_ITEMS_QUERY } from '../queries/index';
+
+// export const itemEnhancer = graphql(ALL_ITEMS_QUERY, {
+// name: 'itemsQuery',
+// options({ page }) {
+// return {
+// variables: ,
+// };
+// },
+// });
const Items = styled.div`
display: grid;
@@ -18,30 +29,38 @@ const Center = styled.div`
`;
class ItemList extends React.Component {
- something() {}
+ static propTypes = {
+ page: PropTypes.number.isRequired,
+ };
+ componentDidUpdate(lastProps) {
+ if (lastProps.page === this.props.page) return;
+ // update the query
+ console.log('This fires, and should update the query');
+ }
render() {
- const { loading, error } = this.props.itemsQuery;
- // 1
- if (loading) {
- return <div>Loading</div>;
- }
-
- // 2
- if (error) {
- console.log(this.props.itemsQuery.error);
- return <div>Error</div>;
- }
- // 3
- const itemsToRender = this.props.itemsQuery.items;
-
return (
- <Center>
+ <Center key={this.props.page}>
<Pagination page={this.props.page} />
- <Items key={this.props.page}>{itemsToRender.map((item, i) => <Item key={item.id} item={item} />)}</Items>
+ <Query
+ query={ALL_ITEMS_QUERY}
+ variables={{
+ skip: this.props.page * perPage - perPage,
+ first: perPage,
+ }}
+ >
+ {({ data, error, loading, variables }) => {
+ if (loading) return <div>Loading</div>;
+ if (error) return <div>Error</div>;
+ return (
+ <Items key={this.props.page}>{data.items.map((item, i) => <Item key={item.id} item={item} />)}</Items>
+ );
+ }}
+ </Query>
<Pagination page={this.props.page} />
</Center>
);
}
}
-export default compose(itemEnhancer)(ItemList);
+// export default compose(itemEnhancer)(ItemList);
+export default ItemList;
diff --git a/frontend/components/Nav.js b/frontend/components/Nav.js
index 3d7f0f6..b898fd5 100644
--- a/frontend/components/Nav.js
+++ b/frontend/components/Nav.js
@@ -1,9 +1,8 @@
import React, { Fragment } from 'react';
import Link from 'next/link';
import styled from 'styled-components';
-import { compose } from 'react-apollo';
-import PropTypes from 'prop-types';
-import { userEnhancer } from '../enhancers/enhancers';
+import { Query } from 'react-apollo';
+import { CURRENT_USER_QUERY } from '../queries/index';
import CartCount from './CartCount';
import Signout from './Signout';
import { UIContext } from './UIContext';
@@ -59,55 +58,51 @@ const StyledUl = styled.ul`
`;
class Nav extends React.Component {
- static propTypes = {
- currentUser: PropTypes.object.isRequired,
- };
- componentDidMount() {
- this.props.currentUser.refetch();
- }
render() {
- const { currentUser } = this.props;
return (
- <StyledUl>
- <Link prefetch href="/items">
- <a>Shop</a>
- </Link>
- <Link prefetch href="/add">
- <a>Sell</a>
- </Link>
-
- {!currentUser.me && (
- <Link prefetch href="/signup">
- <a>Sign In</a>
- </Link>
- )}
-
- {currentUser.me && (
- <Fragment>
- <Link href="/orders">
- <a>Orders</a>
+ <Query query={CURRENT_USER_QUERY}>
+ {({ data: { me } }) => (
+ <StyledUl>
+ <Link prefetch href="/items">
+ <a>Shop</a>
</Link>
- <Link href="/me">
- <a>My Account</a>
+ <Link prefetch href="/add">
+ <a>Sell</a>
</Link>
- <Signout />
- <UIContext>
- {context => (
- <button onClick={context.toggle}>
- My Cart
- <CartCount
- className="cart-count"
- count={currentUser.me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)}
- />
- </button>
- )}
- </UIContext>
- </Fragment>
+
+ {!me && (
+ <Link prefetch 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 />
+ <UIContext>
+ {context => (
+ <button onClick={context.toggle}>
+ My Cart
+ <CartCount
+ className="cart-count"
+ count={me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)}
+ />
+ </button>
+ )}
+ </UIContext>
+ </Fragment>
+ )}
+ </StyledUl>
)}
- </StyledUl>
+ </Query>
);
}
}
-export default compose(userEnhancer)(Nav);
-export { Nav };
+export default Nav;
diff --git a/frontend/components/Order.js b/frontend/components/Order.js
index 409db66..a715483 100644
--- a/frontend/components/Order.js
+++ b/frontend/components/Order.js
@@ -1,60 +1,59 @@
import { Component } from 'react';
+import { Query } from 'react-apollo';
+import { format } from 'date-fns';
+import Head from 'next/head';
+import PropTypes from 'prop-types';
import { SINGLE_ORDER_QUERY } from '../queries';
-import { graphql, compose } from 'react-apollo';
-import { format, formatDistance } from 'date-fns';
import formatMoney from '../lib/formatMoney';
-import Item from './Item';
-import Head from 'next/head';
class Order extends Component {
- componentDidMount() {
- // this.props.orderQuery.refetch();
- }
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ };
+
render() {
- const { order } = this.props.orderQuery;
- if (!order) {
- return <p>No order Found!</p>;
- }
return (
- <div>
- <Head>
- <title>Sick Fits - Order {order.id}</title>
- </Head>
- <p>
- <span>Order Id:</span>
- <span>{order.id}</span>
- </p>
- <p>
- <span>Charge</span>
- <span>{order.charge}</span>
- </p>
- <p>
- <span>Date</span>
- <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span>
- </p>
- <p>
- <span>Order Total</span>
- <span>{formatMoney(order.total)}</span>
- </p>
- <p>You Bought {order.items.length} items in this order</p>
- <div className="items">
- {order.items.map(item => (
- <div className="order-item" key={item.id}>
- <h2>
- {item.quantity} OF {item.title}
- </h2>
+ <Query query={SINGLE_ORDER_QUERY} variables={{ id: this.props.id }}>
+ {({ data: { order }, error, loading }) => {
+ if (loading) return <p>Loading...</p>;
+ if (!order || error) return <p>No Order Found!</p>;
+ return (
+ <div>
+ <Head>
+ <title>Sick Fits - Order {order.id}</title>
+ </Head>
+ <p>
+ <span>Order Id:</span>
+ <span>{order.id}</span>
+ </p>
+ <p>
+ <span>Charge</span>
+ <span>{order.charge}</span>
+ </p>
+ <p>
+ <span>Date</span>
+ <span>{format(order.createdAt, 'MMMM D, YYYY h:mm A')}</span>
+ </p>
+ <p>
+ <span>Order Total</span>
+ <span>{formatMoney(order.total)}</span>
+ </p>
+ <p>You Bought {order.items.length} items in this order</p>
+ <div className="items">
+ {order.items.map(item => (
+ <div className="order-item" key={item.id}>
+ <h2>
+ {item.quantity} OF {item.title}
+ </h2>
+ </div>
+ ))}
+ </div>
</div>
- ))}
- </div>
- </div>
+ );
+ }}
+ </Query>
);
}
}
-const orderEnhancer = graphql(SINGLE_ORDER_QUERY, {
- name: 'orderQuery',
- options: ({ id }) => ({
- variables: { id },
- }),
-});
-export default compose(orderEnhancer)(Order);
+export default Order;
diff --git a/frontend/components/OrderList.js b/frontend/components/OrderList.js
index 6a1ad67..ed7beae 100644
--- a/frontend/components/OrderList.js
+++ b/frontend/components/OrderList.js
@@ -1,56 +1,48 @@
-import { Component } from 'react';
-import { USER_ORDERS_QUERY } from '../queries';
-import { graphql, compose } from 'react-apollo';
+import React from 'react';
+import { Query } from 'react-apollo';
import { format, formatDistance } from 'date-fns';
-import formatMoney from '../lib/formatMoney';
import Link from 'next/link';
+import { USER_ORDERS_QUERY } from '../queries';
-class OrderList extends Component {
- componentDidMount() {
- this.props.userOrdersQuery.refetch();
- }
+class OrderList extends React.Component {
render() {
- if (this.props.loading) {
- return <p>Loading...</p>;
- }
-
- if (this.props.error) {
- return <p>Error...</p>;
- }
-
- const { orders } = this.props.userOrdersQuery;
-
- if (!orders || !orders.length) return <p>No Orders Yet!</p>;
-
return (
- <div>
- <h2>You have {orders.length} Orders</h2>
- <ul>
- {orders.map(order => (
- <li key={order.id}>
- <ul className="order-items">
- <hr />
- <h2>
- <Link
- href={{
- pathname: '/order',
- query: { id: order.id },
- }}
- >
- <a>{order.id}</a>
- </Link>
- <time dateTime={order.createdAt} title={format(order.createdAt, 'MMMM D, YYYY h:mm A')}>
- {formatDistance(order.createdAt, new Date())}
- </time>
- </h2>
+ <Query query={USER_ORDERS_QUERY}>
+ {({ data: { orders }, loading, error }) => {
+ if (loading) return <p>Loading...</p>;
+ if (error) return <p>Error...</p>;
+ if (!orders || !orders.length) return <p>No Orders Yet!</p>;
+ return (
+ <div>
+ <h2>You have {orders.length} Orders</h2>
+ <ul>
+ {orders.map(order => (
+ <li key={order.id}>
+ <ul className="order-items">
+ <hr />
+ <h2>
+ <Link
+ href={{
+ pathname: '/order',
+ query: { id: order.id },
+ }}
+ >
+ <a>{order.id}</a>
+ </Link>
+ <time dateTime={order.createdAt} title={format(order.createdAt, 'MMMM D, YYYY h:mm A')}>
+ {formatDistance(order.createdAt, new Date())}
+ </time>
+ </h2>
+ </ul>
+ </li>
+ ))}
</ul>
- </li>
- ))}
- </ul>
- </div>
+ </div>
+ );
+ }}
+ </Query>
);
}
}
-const ordersEnhancer = graphql(USER_ORDERS_QUERY, { name: 'userOrdersQuery' });
-export default compose(ordersEnhancer)(OrderList);
+export default OrderList;
diff --git a/frontend/components/Page.js b/frontend/components/Page.js
index 36854ef..bfe8492 100644
--- a/frontend/components/Page.js
+++ b/frontend/components/Page.js
@@ -53,9 +53,8 @@ const Page = ({ children }) => (
</ThemeProvider>
</UIProvider>
);
-
Page.propTypes = {
- children: PropTypes.object.isRequired,
+ children: PropTypes.node.isRequired,
};
export default Page;
diff --git a/frontend/components/Pagination.js b/frontend/components/Pagination.js
index abc22d0..138e1b2 100644
--- a/frontend/components/Pagination.js
+++ b/frontend/components/Pagination.js
@@ -1,10 +1,11 @@
import React from 'react';
-import { compose } from 'react-apollo';
+import { compose, Query } from 'react-apollo';
import styled from 'styled-components';
import Link from 'next/link';
import PropTypes from 'prop-types';
import { itemEnhancer } from '../enhancers/enhancers';
import { perPage } from '../config';
+import { ALL_ITEMS_QUERY } from '../queries/index';
const PaginationStyles = styled.div`
text-align: center;
@@ -30,56 +31,51 @@ const PaginationStyles = styled.div`
}
`;
-const Pagination = props => {
- if (props.loading) return <p>Loading...</p>;
- const { aggregate } = props.itemsQuery.itemsConnection;
- const { page } = props;
- const pages = Math.ceil(aggregate.count / perPage);
-
- return (
- <PaginationStyles>
- <Link
- prefetch
- href={{
- pathname: 'items',
- query: { page: page - 1 },
- }}
- >
- <a className="prev" aria-disabled={page <= 1}>
- โ†Prev
- </a>
- </Link>
- <p>
- Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong>
- </p>
- <p>
- <strong>{aggregate.count}</strong> Items Total
- </p>
- <Link
- prefetch
- href={{
- pathname: 'items',
- query: { page: page + 1 },
- }}
- >
- <a className="next" aria-disabled={page >= pages}>
- Next โ†’
- </a>
- </Link>
- </PaginationStyles>
- );
-};
+const Pagination = props => (
+ <Query query={ALL_ITEMS_QUERY}>
+ {({ data, loading, error }) => {
+ if (loading) return null;
+ const { aggregate } = data.itemsConnection;
+ const { page } = props;
+ const pages = Math.ceil(aggregate.count / perPage);
+ return (
+ <PaginationStyles>
+ <Link
+ prefetch
+ href={{
+ pathname: 'items',
+ query: { page: page - 1 },
+ }}
+ >
+ <a className="prev" aria-disabled={page <= 1}>
+ โ†Prev
+ </a>
+ </Link>
+ <p>
+ Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong>
+ </p>
+ <p>
+ <strong>{aggregate.count}</strong> Items Total
+ </p>
+ <Link
+ prefetch
+ href={{
+ pathname: 'items',
+ query: { page: page + 1 },
+ }}
+ >
+ <a className="next" aria-disabled={page >= pages}>
+ Next โ†’
+ </a>
+ </Link>
+ </PaginationStyles>
+ );
+ }}
+ </Query>
+);
Pagination.propTypes = {
- itemsQuery: PropTypes.shape({
- itemsConnection: PropTypes.shape({
- aggregate: PropTypes.shape({
- count: PropTypes.number.isRequired,
- }),
- }),
- }).isRequired,
page: PropTypes.number.isRequired,
};
-export default compose(itemEnhancer)(Pagination);
-export { Pagination };
+export default Pagination;
diff --git a/frontend/components/RemoveFromCart.js b/frontend/components/RemoveFromCart.js
index 235ecce..d8c81b0 100644
--- a/frontend/components/RemoveFromCart.js
+++ b/frontend/components/RemoveFromCart.js
@@ -1,8 +1,8 @@
import { Component } from 'react';
-import { compose } from 'react-apollo';
+import { Mutation } from 'react-apollo';
import styled from 'styled-components';
import PropTypes from 'prop-types';
-import { removeFromCartEnhancer } from '../enhancers/enhancers';
+import { REMOVE_FROM_CART_MUTATION, CURRENT_USER_QUERY } from '../queries/index';
const BigButton = styled.button`
font-size: 3rem;
@@ -16,26 +16,27 @@ const BigButton = styled.button`
class RemoveFromCart extends Component {
static propTypes = {
- removeFromCart: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
};
- handleRemoveFromCart = async () => {
- await this.props.removeFromCart({
- variables: {
- id: this.props.id,
- },
- });
+ update = (proxy, payload) => {
+ const data = proxy.readQuery({ query: CURRENT_USER_QUERY });
+ const cartItemId = payload.data.removeFromCart.id;
+ data.me.cart = data.me.cart.filter(item => item.id !== cartItemId);
+ proxy.writeQuery({ query: CURRENT_USER_QUERY, data });
};
render() {
return (
- <BigButton title="Remove From Cart" onClick={this.handleRemoveFromCart}>
- &times;
- </BigButton>
+ <Mutation mutation={REMOVE_FROM_CART_MUTATION} variables={{ id: this.props.id }} update={this.update}>
+ {removeFromCart => (
+ <BigButton title="Remove From Cart" onClick={removeFromCart}>
+ &times;
+ </BigButton>
+ )}
+ </Mutation>
);
}
}
-export default compose(removeFromCartEnhancer)(RemoveFromCart);
-export { RemoveFromCart };
+export default RemoveFromCart;
diff --git a/frontend/components/Reset.js b/frontend/components/Reset.js
index 5593f75..5bc7f98 100644
--- a/frontend/components/Reset.js
+++ b/frontend/components/Reset.js
@@ -1,13 +1,18 @@
-import React, { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
-import { RESET_MUTATION, CURRENT_USER_QUERY } from '../queries';
+import React from 'react';
+import { Mutation } from 'react-apollo';
+import PropTypes from 'prop-types';
import Form from './styles/Form';
+import Error from './ErrorMessage';
+import { RESET_MUTATION, CURRENT_USER_QUERY } from '../queries';
+
+class Reset extends React.Component {
+ static propTypes = {
+ resetToken: PropTypes.string.isRequired,
+ };
-class Reset extends Component {
state = {
confirmPassword: '',
password: '',
- errors: [],
};
saveToState = e => {
@@ -15,74 +20,58 @@ class Reset extends Component {
this.setState({ [name]: value });
};
- resetPassword = async e => {
- console.log(e);
+ resetPassword = async (e, resetMutation) => {
e.preventDefault();
-
- if (this.state.password !== this.state.confirmPassword) {
- this.setState({ errors: [{ message: 'Passwords Must Match!' }] });
- }
-
- const res = await this.props.resetPassword({
- variables: {
- resetToken: this.props.resetToken,
- password: this.state.password,
- confirmPassword: this.state.confirmPassword,
- },
- });
-
- if (res.errors) {
- this.setState({ errors: res.errors });
- return;
- }
- console.log('Back!');
+ const res = await resetMutation();
console.log(res);
// sign them in!
localStorage.setItem('token', res.data.resetPassword.token);
- // refresh the current user query
- this.props.currentUser.refetch();
+ // TODO: Manually call refresh on a Query
};
render() {
return (
- <div>
- {this.state.loading ? 'LOADING...' : 'Ready!'}
-
- {this.state.errors ? this.state.errors.map((err, i) => <p key={i}>{err.message}</p>) : null}
-
- <Form onSubmit={this.resetPassword}>
- <label htmlFor="password">
- password
- <input
- value={this.state.password}
- onChange={this.saveToState}
- name="password"
- type="password"
- id="password"
- />
- </label>
- <label htmlFor="confirm">
- Confirm:
- <input
- value={this.state.confirmPassword}
- onChange={this.saveToState}
- name="confirmPassword"
- type="password"
- id="confirmPassword"
- />
- </label>
+ <Mutation
+ mutation={RESET_MUTATION}
+ variables={{
+ resetToken: this.props.resetToken,
+ password: this.state.password,
+ confirmPassword: this.state.confirmPassword,
+ }}
+ refetchQueries={[{ query: CURRENT_USER_QUERY }]}
+ >
+ {(resetMutation, { error, loading }) => (
+ <Form onSubmit={e => this.resetPassword(e, resetMutation)}>
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <label htmlFor="password">
+ password
+ <input
+ value={this.state.password}
+ onChange={this.saveToState}
+ name="password"
+ type="password"
+ id="password"
+ />
+ </label>
+ <label htmlFor="confirm">
+ Confirm:
+ <input
+ value={this.state.confirmPassword}
+ onChange={this.saveToState}
+ name="confirmPassword"
+ type="password"
+ id="confirmPassword"
+ />
+ </label>
- <button type="submit">Request Reset!</button>
- </Form>
- </div>
+ <button type="submit">Request Reset!</button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
);
}
}
-// When we submit this mutation, we need to update our store - we have a few ways to do that:
-// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency.
-
-const restEnahncer = graphql(RESET_MUTATION, { name: 'resetPassword' });
-const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
-
-export default compose(restEnahncer, userEnhancer)(Reset);
+export default Reset;
diff --git a/frontend/components/ResetRequest.js b/frontend/components/ResetRequest.js
index 0910e5f..fb6f660 100644
--- a/frontend/components/ResetRequest.js
+++ b/frontend/components/ResetRequest.js
@@ -1,52 +1,45 @@
-import React, { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
-import PropTypes from 'prop-types';
+import React from 'react';
+import { Mutation } from 'react-apollo';
import { REQUEST_RESET_MUTATION } from '../queries';
import Form from './styles/Form';
+import Error from './ErrorMessage';
-class ResetRequest extends Component {
- static propTypes = {
- requestReset: PropTypes.func.isRequired,
- };
-
+class ResetRequest extends React.Component {
state = {
email: '',
- errors: [],
- };
-
- requestReset = async e => {
- e.preventDefault();
- const res = await this.props.requestReset({
- variables: {
- email: this.state.email,
- },
- });
- if (res.errors) {
- this.setState({ errors: res.errors });
- }
- };
-
- saveToState = e => {
- const { name, value } = e.target;
- this.setState({ [name]: value });
};
render() {
return (
- <Form onSubmit={this.requestReset}>
- {this.state.errors ? this.state.errors.map((err, i) => <p key={i}>{err.message}</p>) : null}
- <label htmlFor="email">
- Email
- <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" />
- </label>
+ <Mutation mutation={REQUEST_RESET_MUTATION} variables={this.state}>
+ {(resetMutation, { loading, error }) => (
+ <Form
+ onSubmit={async e => {
+ e.preventDefault();
+ const res = await resetMutation();
+ console.log(res);
+ }}
+ >
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <label htmlFor="email">
+ Email
+ <input
+ value={this.state.email}
+ onChange={e => this.setState({ email: e.target.value })}
+ name="email"
+ type="text"
+ placeholder="email"
+ />
+ </label>
- <button type="submit">Request Reset!</button>
- </Form>
+ <button type="submit">Request Reset!</button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
);
}
}
-const requestResetEnhancer = graphql(REQUEST_RESET_MUTATION, { name: 'requestReset' });
-
-export default compose(requestResetEnhancer)(ResetRequest);
-export { ResetRequest };
+export default ResetRequest;
diff --git a/frontend/components/Search.js b/frontend/components/Search.js
index d30789c..45f5961 100644
--- a/frontend/components/Search.js
+++ b/frontend/components/Search.js
@@ -1,15 +1,16 @@
import Downshift from 'downshift';
-import { graphql, compose } from 'react-apollo';
+import { Query } from 'react-apollo';
+import Router from 'next/router';
import styled from 'styled-components';
import { SEARCH_ITEMS_QUERY } from '../queries';
-import { Router } from '../routes';
function routeToItem(item) {
- console.log('TODO: UPdate routeToItem function');
- // Router.pushRoute('item', {
- // slug: slugify(item.title),
- // itemId: item.id,
- // });
+ Router.push({
+ pathname: '/item',
+ query: {
+ id: item.id,
+ },
+ });
}
const DropDown = styled.div`
@@ -52,7 +53,6 @@ function AutoComplete(props) {
placeholder: 'Search For Item',
id: 'search',
onChange: e => {
- console.log('Change happened');
props.refetch({ searchTerm: e.target.value });
},
style: { fontSize: '20px', padding: '10px', display: 'block', width: '100%' },
@@ -75,21 +75,15 @@ function AutoComplete(props) {
);
}
-const Search = props => (
+// TODO: This should not fire a queryon page load
+const Search = () => (
<SearchStyles>
- <AutoComplete
- items={props.searchItems.items}
- onChange={selectedItem => console.log(selectedItem)}
- refetch={props.searchItems.refetch}
- />
+ <Query query={SEARCH_ITEMS_QUERY} variables={{ searchTerm: '' }}>
+ {({ data, error, refetch }) => (
+ <AutoComplete items={data.items || []} onChange={selectedItem => console.log(selectedItem)} refetch={refetch} />
+ )}
+ </Query>
</SearchStyles>
);
-const searchEnhancer = graphql(SEARCH_ITEMS_QUERY, {
- name: 'searchItems',
- options: {
- // TODO: Why is this camo
- variables: { searchTerm: 'camo' },
- },
-});
-export default compose(searchEnhancer)(Search);
+export default Search;
diff --git a/frontend/components/Signin.js b/frontend/components/Signin.js
index 423e9e8..a211528 100644
--- a/frontend/components/Signin.js
+++ b/frontend/components/Signin.js
@@ -1,34 +1,21 @@
import React, { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
+import { Mutation } from 'react-apollo';
import { SIGNIN_MUTATION, CURRENT_USER_QUERY } from '../queries';
-
+import Error from './ErrorMessage';
import Form from './styles/Form';
class Signin extends Component {
state = {
email: `wesbos@gmail.com`,
password: 'abc123',
- errors: [],
};
- loginUser = async e => {
+ loginUser = async (e, signin) => {
e.preventDefault();
- // pull the values from state
- const { email, password } = this.state;
- this.setState({ loading: true, errors: [] });
- const res = await this.props.signin({
- // pass in those variables from state
- variables: { name, email, password },
- });
- if (res.errors) {
- this.setState({ errors: res.errors });
- return;
- }
+ const res = await signin();
localStorage.setItem('token', res.data.signin.token);
+ return Promise.resolve('it worked');
// TODO refetch current user query
- this.props.currentUser.refetch();
-
- this.setState({ loading: false });
};
saveToState = e => {
@@ -38,41 +25,42 @@ class Signin extends Component {
render() {
return (
- <div>
- {this.state.loading ? 'LOADING...' : null}
-
- {this.state.errors ? this.state.errors.map(err => <p>{err.message}</p>) : null}
+ <Mutation mutation={SIGNIN_MUTATION} variables={this.state} refetchQueries={[{ query: CURRENT_USER_QUERY }]}>
+ {(signin, { data, loading, error }) => (
+ <Form onSubmit={e => this.loginUser(e, signin)}>
+ <Error error={error} />
+ <fieldset disabled={loading} aria-busy={loading}>
+ <label htmlFor="email">
+ Email
+ <input
+ value={this.state.email}
+ onChange={this.saveToState}
+ name="email"
+ type="text"
+ placeholder="email"
+ />
+ </label>
- <Form onSubmit={this.loginUser}>
- <label htmlFor="email">
- Email
- <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" />
- </label>
+ <label htmlFor="password">
+ Password
+ <input
+ type="password"
+ name="password"
+ id="password"
+ className="password"
+ placeholder="password"
+ value={this.state.password}
+ onChange={this.saveToState}
+ />
+ </label>
- <label htmlFor="password">
- Password
- <input
- type="password"
- name="password"
- id="password"
- className="password"
- placeholder="password"
- value={this.state.password}
- onChange={this.saveToState}
- />
- </label>
-
- <button type="submit">Sign In!</button>
- </Form>
- </div>
+ <button type="submit">Sign In!</button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
);
}
}
-// When we submit this mutation, we need to update our store - we have a few ways to do that:
-// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency.
-
-const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
-const signinEnhancer = graphql(SIGNIN_MUTATION, { name: 'signin' });
-
-export default compose(signinEnhancer, userEnhancer)(Signin);
+export default Signin;
diff --git a/frontend/components/Signout.js b/frontend/components/Signout.js
index 8581009..d7c7816 100644
--- a/frontend/components/Signout.js
+++ b/frontend/components/Signout.js
@@ -1,20 +1,21 @@
import React, { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
+import { Query } from 'react-apollo';
import { CURRENT_USER_QUERY } from '../queries';
class Signout extends Component {
- signout = () => {
+ signout = refetch => {
console.log('Signing Out');
localStorage.removeItem('token');
- this.props.currentUser.refetch();
+ refetch();
};
render() {
- const { me, loading, error } = this.props.currentUser;
- if (!me || loading || error) return null;
- return <button onClick={this.signout}>Sign Out</button>;
+ return (
+ <Query query={CURRENT_USER_QUERY}>
+ {({ refetch }) => <button onClick={() => this.signout(refetch)}>Sign Out</button>}
+ </Query>
+ );
}
}
-const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
-export default compose(userEnhancer)(Signout);
+export default Signout;
diff --git a/frontend/components/Signup.js b/frontend/components/Signup.js
index 3a4dd99..314d0ab 100644
--- a/frontend/components/Signup.js
+++ b/frontend/components/Signup.js
@@ -1,38 +1,15 @@
import React, { Component } from 'react';
-import { graphql, compose } from 'react-apollo';
+import { graphql, compose, Mutation } from 'react-apollo';
import { SIGNUP_MUTATION, CURRENT_USER_QUERY } from '../queries';
import Form from './styles/Form';
+import catchError from '../lib/catchError';
+import Error from './ErrorMessage';
class Signup extends Component {
state = {
- email: `wesbos+${Date.now()}@gmail.com`,
+ email: `wesbos@gmail.com`,
name: 'Wes Bos',
- password: 'abc123',
- error: undefined,
- };
-
- createUser = async e => {
- e.preventDefault();
- // pull the values from state
- const { email, name, password } = this.state;
- // create a mutation
- // TODO: handle any errors
- // turn loading on
- this.setState({ loading: true });
- console.log({ name, email, password });
- try {
- const res = await this.props.signup({
- // pass in those variables from state
- variables: { name, email, password },
- });
- localStorage.setItem('token', res.data.signup.token);
- // TODO refetch current user query
- this.props.currentUser.refetch();
- } catch (error) {
- this.setState({ error });
- console.dir(error);
- }
- this.setState({ loading: false });
+ password: 'wes',
};
saveToState = e => {
@@ -42,52 +19,53 @@ class Signup extends Component {
render() {
return (
- <div>
- {this.state.loading ? 'LOADING...' : null}
-
- {this.state.error ? <p>{this.state.error.message}</p> : null}
-
- <Form onSubmit={this.createUser}>
- <label htmlFor="email">
- Email
- <input value={this.state.email} onChange={this.saveToState} name="email" type="text" placeholder="email" />
- </label>
+ <Mutation mutation={SIGNUP_MUTATION} variables={this.state}>
+ {(signup, { data, loading, error }) => (
+ <Form
+ onSubmit={e => {
+ e.preventDefault();
+ signup();
+ }}
+ >
+ <fieldset disabled={loading} aria-busy={loading}>
+ <Error error={error} />
+ <h2>Sign Up for an Account</h2>
+ <label htmlFor="email">
+ Email
+ <input
+ value={this.state.email}
+ onChange={this.saveToState}
+ name="email"
+ type="text"
+ placeholder="email"
+ />
+ </label>
- <label htmlFor="name">
- Name
- <input type="text" name="name" placeholder="name" value={this.state.name} onChange={this.saveToState} />
- </label>
+ <label htmlFor="name">
+ Name
+ <input type="text" name="name" placeholder="name" value={this.state.name} 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}
- />
- </label>
+ <label htmlFor="signupPassword">
+ Password
+ <input
+ type="password"
+ name="password"
+ id="signupPassword"
+ className="password"
+ placeholder="password"
+ value={this.state.password}
+ onChange={this.saveToState}
+ />
+ </label>
- <button type="submit">Submit</button>
- </Form>
- </div>
+ <button type="submit">Submit</button>
+ </fieldset>
+ </Form>
+ )}
+ </Mutation>
);
}
}
-// When we submit this mutation, we need to update our store - we have a few ways to do that:
-// One - we can go nucular and run refetchQueries() which will just go get everything - this is easy, but at the cost of efficiency.
-
-const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
-
-const signupEnhancer = graphql(SIGNUP_MUTATION, {
- name: 'signup',
- options: {
- refetchQueries: ['currentUser'],
- },
-});
-
-export default compose(userEnhancer, signupEnhancer)(Signup);
+export default Signup;
diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js
index 437fff1..5a6373f 100644
--- a/frontend/components/SingleItem.js
+++ b/frontend/components/SingleItem.js
@@ -6,6 +6,10 @@ const SingleItem = ({ findItem: { error, loading, items } }) => {
if (error) return <p>Error...</p>;
const item = items[0];
+ if (!item) {
+ return <p>No Item Found</p>;
+ }
+
return (
<div>
<img src={item.largeImage || item.image} alt={item.title} />
diff --git a/frontend/components/TakeMyMoney.js b/frontend/components/TakeMyMoney.js
index 011451a..20fc334 100644
--- a/frontend/components/TakeMyMoney.js
+++ b/frontend/components/TakeMyMoney.js
@@ -1,16 +1,23 @@
import { Component } from 'react';
import StripeCheckout from 'react-stripe-checkout';
-import { compose } from 'react-apollo';
+import { compose, Mutation, Query } from 'react-apollo';
import Router from 'next/router';
import NProgress from 'nprogress';
import PropTypes from 'prop-types';
-// import { CREATE_ORDER_MUTATION, CURRENT_USER_QUERY } from '../queries';
-import { userEnhancer, createOrderEnhancer } from '../enhancers/enhancers';
+import { CREATE_ORDER_MUTATION, CURRENT_USER_QUERY } from '../queries';
+
+function totalItems(cart) {
+ return cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0);
+}
+
+function total(cart) {
+ return cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0);
+}
class TakeMyMoney extends Component {
- onToken = async res => {
+ onToken = async (res, createOrder) => {
NProgress.start();
- const order = await this.props.createOrder({
+ const order = await createOrder({
variables: {
token: res.id,
},
@@ -18,29 +25,33 @@ class TakeMyMoney extends Component {
// Route them to that order page
const { id } = order.data.createOrder;
Router.push({
- pathname: `/order/${id}`,
+ pathname: `/order`,
query: { id },
});
};
-
+ // TODO - refetchQueries after an order is created
render() {
- const { me } = this.props.currentUser;
- if (!me || !me.cart.length) return null;
- const total = me.cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0);
- const totalItems = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0);
return (
- <StripeCheckout
- amount={total}
- name="Sick Fits Haul"
- description={`Order of ${totalItems} Items From Sick Fits`}
- image={me.cart[0].item.image}
- token={this.onToken}
- stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps"
- currency="USD"
- email={me.email}
- >
- {this.props.children}
- </StripeCheckout>
+ <Query query={CURRENT_USER_QUERY}>
+ {({ data: { me } }) => (
+ <Mutation mutation={CREATE_ORDER_MUTATION}>
+ {createOrder => (
+ <StripeCheckout
+ amount={total(me.cart)}
+ name="Sick Fits Haul"
+ description={`Order of ${totalItems(me.cart)} Items From Sick Fits`}
+ image={me.cart[0].item.image}
+ token={res => this.onToken(res, createOrder)}
+ stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps"
+ currency="USD"
+ email={me.email}
+ >
+ {this.props.children}
+ </StripeCheckout>
+ )}
+ </Mutation>
+ )}
+ </Query>
);
}
}
@@ -50,5 +61,7 @@ TakeMyMoney.propTypes = {
createOrder: PropTypes.func.isRequired,
};
-export default compose(userEnhancer, createOrderEnhancer)(TakeMyMoney);
-export { TakeMyMoney };
+// export default compose(userEnhancer, createOrderEnhancer)(TakeMyMoney);
+// export { TakeMyMoney };
+
+export default TakeMyMoney;
diff --git a/frontend/components/UpdateItem.js b/frontend/components/UpdateItem.js
index e97b4aa..4841006 100644
--- a/frontend/components/UpdateItem.js
+++ b/frontend/components/UpdateItem.js
@@ -9,9 +9,7 @@ class UpdateItem extends Component {
state = {
item: {},
};
- componentDidMount() {
- console.log(this.props);
- }
+
saveToState = e => {
let { name, value, type } = e.target;
diff --git a/frontend/components/styles/Form.js b/frontend/components/styles/Form.js
index 09085ad..4fd8029 100644
--- a/frontend/components/styles/Form.js
+++ b/frontend/components/styles/Form.js
@@ -1,11 +1,22 @@
-import styled from 'styled-components';
+import styled, { keyframes } from 'styled-components';
+
+const loading = keyframes`
+ from {
+ background-position: 0 0;
+ /* rotate: 0; */
+ }
+
+ to {
+ background-position: 100% 100%;
+ /* rotate: 360deg; */
+ }
+`;
const Form = styled.form`
box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.05);
background: rgba(0, 0, 0, 0.02);
border: 5px solid white;
- margin: 100px;
- padding: 100px;
+ padding: 20px;
font-size: 2.5rem;
font-weight: 600;
label {
@@ -36,6 +47,24 @@ const Form = styled.form`
font-weight: 600;
padding: 1rem 2rem;
}
+ fieldset {
+ border: 0;
+ padding: 0;
+
+ &[disabled] {
+ opacity: 0.5;
+ }
+ &::before {
+ height: 10px;
+ content: '';
+ display: block;
+ background-image: linear-gradient(to right, #ff3019 0%, #e2b04a 50%, #ff3019 100%);
+ }
+ &[aria-busy='true']::before {
+ background-size: 50% auto;
+ animation: ${loading} 0.5s linear infinite;
+ }
+ }
`;
Form.displayName = 'Form';