summaryrefslogtreecommitdiffstats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/.babelrc9
-rw-r--r--frontend/__tests__/CartCount.test.js21
-rw-r--r--frontend/__tests__/CreateItem.test.js85
-rw-r--r--frontend/__tests__/Item.test.js31
-rw-r--r--frontend/__tests__/Nav.test.js35
-rw-r--r--frontend/__tests__/Pagination.test.js46
-rw-r--r--frontend/__tests__/__snapshots__/CartCount.test.js.snap201
-rw-r--r--frontend/__tests__/__snapshots__/CreateItem.test.js.snap58
-rw-r--r--frontend/__tests__/__snapshots__/Item.test.js.snap60
-rw-r--r--frontend/__tests__/__snapshots__/Nav.test.js.snap69
-rw-r--r--frontend/__tests__/__snapshots__/Pagination.test.js.snap67
-rw-r--r--frontend/__tests__/formatMoney.test.js21
-rw-r--r--frontend/components/AddToCart.js41
-rw-r--r--frontend/components/CartCount.js1
-rw-r--r--frontend/components/Count.js4
-rw-r--r--frontend/components/CreateItem.js70
-rw-r--r--frontend/components/Item.js5
-rw-r--r--frontend/components/Items.js3
-rw-r--r--frontend/components/Nav.js9
-rw-r--r--frontend/components/Pagination.js32
-rw-r--r--frontend/components/Search.js10
-rw-r--r--frontend/components/SingleItem.js18
-rw-r--r--frontend/components/styles/Form.js3
-rw-r--r--frontend/enhancers/enhancers.js2
-rw-r--r--frontend/lib/formatMoney.js12
-rw-r--r--frontend/package.json20
26 files changed, 815 insertions, 118 deletions
diff --git a/frontend/.babelrc b/frontend/.babelrc
deleted file mode 100644
index d9c3f91..0000000
--- a/frontend/.babelrc
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "presets": ["next/babel"],
- "plugins": [
- [
- "styled-components",
- { "ssr": true, "displayName": true, "preprocess": false }
- ]
- ]
-}
diff --git a/frontend/__tests__/CartCount.test.js b/frontend/__tests__/CartCount.test.js
new file mode 100644
index 0000000..f3c4978
--- /dev/null
+++ b/frontend/__tests__/CartCount.test.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import toJSON from 'enzyme-to-json';
+import { CartCount } from '../components/CartCount';
+
+describe('<CartCount></CartCount>', () => {
+ it('renders okay', () => {
+ shallow(<CartCount count="10" />);
+ });
+
+ it('matches snapshot', () => {
+ const wrapper = shallow(<CartCount count="10" />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+ it('updates via props', () => {
+ const wrapper = mount(<CartCount count="50" />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ wrapper.setProps({ count: 10 });
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+});
diff --git a/frontend/__tests__/CreateItem.test.js b/frontend/__tests__/CreateItem.test.js
new file mode 100644
index 0000000..0994d9c
--- /dev/null
+++ b/frontend/__tests__/CreateItem.test.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import toJSON from 'enzyme-to-json';
+import { CreateItem } from '../components/CreateItem';
+
+// Wait func
+const wait = amount => new Promise(resolve => setTimeout(resolve, amount));
+
+const dogImage = 'https://dog.com/dog.jpg';
+
+// Mock fetch to resolve data
+global.fetch = jest.fn().mockResolvedValue({
+ json: () => ({
+ secure_url: dogImage,
+ eager: [{ secure_url: dogImage }],
+ }),
+});
+
+const fakeItem = {
+ title: 'Testing',
+ description: 'This is a really nice item',
+ image: dogImage,
+ largeImage: dogImage,
+ price: 50000,
+};
+
+describe('<Createitem/>', () => {
+ it('renders the form out', () => {
+ const createItemMutation = jest.fn();
+ const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+
+ it('uploads a file when changed', async () => {
+ const createItemMutation = jest.fn();
+ const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />);
+
+ const input = wrapper.find('input[type="file"]');
+ // internally this does some setState() calls
+ input.simulate('change', { currentTarget: { files: ['fakedog.jpg'] } });
+ await wait(0);
+ expect(wrapper.state('image')).toEqual(dogImage);
+ expect(wrapper.state('largeImage')).toEqual(dogImage);
+ });
+
+ it('handles state updating', async () => {
+ const createItemMutation = jest.fn();
+ const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />);
+
+ wrapper.find('#title').simulate('change', { target: { value: 'Testing', name: 'title' } });
+ wrapper.find('#price').simulate('change', {
+ target: {
+ value: 50000,
+ name: 'price',
+ type: 'number',
+ },
+ });
+ wrapper.find('#description').simulate('change', {
+ target: {
+ value: 'This is a really nice item',
+ name: 'description',
+ },
+ });
+ expect(wrapper.state().item).toMatchObject({
+ title: 'Testing',
+ description: 'This is a really nice item',
+ image: '',
+ largeImage: '',
+ price: 50000,
+ });
+ });
+
+ it('creates an item with all the inputs', () => {
+ const createItemMutation = jest.fn(() => Promise.resolve({}));
+ const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />);
+ wrapper.setState({ item: fakeItem });
+ const form = wrapper.find('Form');
+ // console.log(form.debug());
+ form.simulate('submit', {
+ preventDefault() {},
+ });
+ expect(createItemMutation).toHaveBeenCalled();
+ expect(createItemMutation).toHaveBeenCalledWith({ variables: fakeItem });
+ });
+});
diff --git a/frontend/__tests__/Item.test.js b/frontend/__tests__/Item.test.js
new file mode 100644
index 0000000..2990b1f
--- /dev/null
+++ b/frontend/__tests__/Item.test.js
@@ -0,0 +1,31 @@
+/* eslint-env jest */
+
+import React from 'react';
+import Enzyme, { shallow, mount } from 'enzyme';
+import toJSON from 'enzyme-to-json';
+import { ItemComponent } from '../components/Item';
+
+const fakeItem = {
+ id: 'ABC123',
+ title: 'A Cool Item',
+ price: 5000,
+ description: 'This item is really cool!',
+ image: 'dog.jpg',
+ largeImage: 'largedog.jpg',
+};
+
+describe('<Item/>', () => {
+ it('Renders an item', () => {
+ const removeItem = jest.fn();
+ const wrapper = shallow(<ItemComponent item={fakeItem} removeItem={removeItem} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+ it('handles button clicks', async () => {
+ const removeItem = jest.fn();
+ global.confirm = jest.fn().mockReturnValue(true);
+ const wrapper = shallow(<ItemComponent item={fakeItem} removeItem={removeItem} />);
+ wrapper.find('button').simulate('click');
+ expect(removeItem).toHaveBeenCalled();
+ expect(global.confirm).toHaveBeenCalled();
+ });
+});
diff --git a/frontend/__tests__/Nav.test.js b/frontend/__tests__/Nav.test.js
new file mode 100644
index 0000000..49d16d6
--- /dev/null
+++ b/frontend/__tests__/Nav.test.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import toJSON from 'enzyme-to-json';
+import { Nav } from '../components/Nav';
+
+const currentUserLoggedOut = {
+ refetch() {},
+};
+
+const currentUser = {
+ me: {},
+ refetch() {},
+};
+
+describe('<Nav></Nav>', () => {
+ it('renders', () => {
+ shallow(<Nav currentUser={currentUserLoggedOut} />);
+ });
+
+ it('Renders minimal nav when logged out', () => {
+ const wrapper = shallow(<Nav currentUser={currentUserLoggedOut} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+
+ it('renders full nav when logged in', () => {
+ const wrapper = shallow(<Nav currentUser={currentUser} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+
+ it('tries to refetch the current user when it mounts', () => {
+ const refetch = jest.fn();
+ shallow(<Nav currentUser={{ ...currentUser, refetch }} />);
+ expect(refetch).toHaveBeenCalled();
+ });
+});
diff --git a/frontend/__tests__/Pagination.test.js b/frontend/__tests__/Pagination.test.js
new file mode 100644
index 0000000..e2ef7af
--- /dev/null
+++ b/frontend/__tests__/Pagination.test.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import { shallow, mount } from 'enzyme';
+import toJSON from 'enzyme-to-json';
+import { Pagination } from '../components/Pagination';
+
+const fakeQuery = {
+ itemsConnection: { aggregate: { count: 18 } },
+};
+
+describe('<Pagination/>', () => {
+ it('displays loading message', () => {
+ const wrapper = shallow(<Pagination loading page={1} itemsQuery={fakeQuery} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+
+ it('renders pagination for 18 items', () => {
+ const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery} />);
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ expect(wrapper.find('.totalPages').text()).toEqual('2');
+ });
+
+ it('renders pagination for 28 items', () => {
+ const fakeQuery2 = {
+ itemsConnection: { aggregate: { count: 28 } },
+ };
+ const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery2} />);
+ expect(wrapper.find('.totalPages').text()).toEqual('4');
+ });
+
+ it('disables and enables next/prev buttons', () => {
+ const fakeQuery3 = {
+ itemsConnection: { aggregate: { count: 100 } },
+ };
+ const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery} />);
+
+ expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(true);
+ expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(false);
+ wrapper.setProps({ page: 2 });
+ expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(false);
+ expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(true);
+ // when in the middle, both should work
+ wrapper.setProps({ itemsQuery: fakeQuery3, page: 3 });
+ expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(false);
+ expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(false);
+ });
+});
diff --git a/frontend/__tests__/__snapshots__/CartCount.test.js.snap b/frontend/__tests__/__snapshots__/CartCount.test.js.snap
new file mode 100644
index 0000000..ddb6710
--- /dev/null
+++ b/frontend/__tests__/__snapshots__/CartCount.test.js.snap
@@ -0,0 +1,201 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<CartCount></CartCount> matches snapshot 1`] = `
+<styled.span>
+ <TransitionGroup
+ childFactory={[Function]}
+ component="div"
+ >
+ <CSSTransition
+ className="count"
+ classNames="count"
+ key="10"
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ >
+ <styled.div>
+ 10
+ </styled.div>
+ </CSSTransition>
+ </TransitionGroup>
+</styled.span>
+`;
+
+exports[`<CartCount></CartCount> updates via props 1`] = `
+<CartCount
+ count="50"
+>
+ <styled.span>
+ <span
+ className="sc-bwzfXH fWZfOZ"
+ >
+ <TransitionGroup
+ childFactory={[Function]}
+ component="div"
+ >
+ <div>
+ <CSSTransition
+ className="count"
+ classNames="count"
+ in={true}
+ key=".$50"
+ onExited={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ >
+ <Transition
+ appear={false}
+ className="count"
+ enter={true}
+ exit={true}
+ in={true}
+ mountOnEnter={false}
+ onEnter={[Function]}
+ onEntered={[Function]}
+ onEntering={[Function]}
+ onExit={[Function]}
+ onExited={[Function]}
+ onExiting={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ unmountOnExit={false}
+ >
+ <styled.div
+ className="count"
+ >
+ <div
+ className="count sc-bdVaJa cYFOmX"
+ >
+ 50
+ </div>
+ </styled.div>
+ </Transition>
+ </CSSTransition>
+ </div>
+ </TransitionGroup>
+ </span>
+ </styled.span>
+</CartCount>
+`;
+
+exports[`<CartCount></CartCount> updates via props 2`] = `
+<CartCount
+ count={10}
+>
+ <styled.span>
+ <span
+ className="sc-bwzfXH fWZfOZ"
+ >
+ <TransitionGroup
+ childFactory={[Function]}
+ component="div"
+ >
+ <div>
+ <CSSTransition
+ className="count"
+ classNames="count"
+ in={true}
+ key=".$10"
+ onExited={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ >
+ <Transition
+ appear={false}
+ className="count"
+ enter={true}
+ exit={true}
+ in={true}
+ mountOnEnter={false}
+ onEnter={[Function]}
+ onEntered={[Function]}
+ onEntering={[Function]}
+ onExit={[Function]}
+ onExited={[Function]}
+ onExiting={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ unmountOnExit={false}
+ >
+ <styled.div
+ className="count"
+ >
+ <div
+ className="count sc-bdVaJa cYFOmX"
+ >
+ 10
+ </div>
+ </styled.div>
+ </Transition>
+ </CSSTransition>
+ <CSSTransition
+ className="count"
+ classNames="count"
+ in={false}
+ key=".$50"
+ onExited={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ >
+ <Transition
+ appear={false}
+ className="count"
+ enter={true}
+ exit={true}
+ in={false}
+ mountOnEnter={false}
+ onEnter={[Function]}
+ onEntered={[Function]}
+ onEntering={[Function]}
+ onExit={[Function]}
+ onExited={[Function]}
+ onExiting={[Function]}
+ timeout={
+ Object {
+ "enter": 2000,
+ "exit": 2000,
+ }
+ }
+ unmountOnExit={false}
+ >
+ <styled.div
+ className="count"
+ >
+ <div
+ className="count sc-bdVaJa cYFOmX"
+ >
+ 50
+ </div>
+ </styled.div>
+ </Transition>
+ </CSSTransition>
+ </div>
+ </TransitionGroup>
+ </span>
+ </styled.span>
+</CartCount>
+`;
diff --git a/frontend/__tests__/__snapshots__/CreateItem.test.js.snap b/frontend/__tests__/__snapshots__/CreateItem.test.js.snap
new file mode 100644
index 0000000..66b7965
--- /dev/null
+++ b/frontend/__tests__/__snapshots__/CreateItem.test.js.snap
@@ -0,0 +1,58 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Createitem/> renders the form out 1`] = `
+<div>
+ <Form
+ onSubmit={[Function]}
+ >
+ <DisplayError
+ error={
+ Object {
+ "message": null,
+ }
+ }
+ onButtonClick={[Function]}
+ />
+ <p>
+ Image
+ <input
+ accept=".png, .jpg, .jpeg"
+ onChange={[Function]}
+ type="file"
+ />
+ </p>
+ <p>
+ Title
+ <input
+ id="title"
+ name="title"
+ onChange={[Function]}
+ placeholder="Title"
+ type="text"
+ />
+ </p>
+ <label>
+ Price
+ <input
+ id="price"
+ min="0"
+ name="price"
+ onChange={[Function]}
+ type="number"
+ />
+ </label>
+ <textarea
+ id="description"
+ name="description"
+ onChange={[Function]}
+ placeholder="The desc for this item"
+ />
+ <button
+ disabled={false}
+ type="submit"
+ >
+ Submit
+ </button>
+ </Form>
+</div>
+`;
diff --git a/frontend/__tests__/__snapshots__/Item.test.js.snap b/frontend/__tests__/__snapshots__/Item.test.js.snap
new file mode 100644
index 0000000..f088121
--- /dev/null
+++ b/frontend/__tests__/__snapshots__/Item.test.js.snap
@@ -0,0 +1,60 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Item/> Renders an item 1`] = `
+<styled.div
+ key="ABC123"
+>
+ <img
+ alt="A Cool Item"
+ src="dog.jpg"
+ />
+ <styled.h3>
+ <Link
+ href={
+ Object {
+ "pathname": "/item",
+ "query": Object {
+ "id": "ABC123",
+ },
+ }
+ }
+ >
+ <a>
+ A Cool Item
+ </a>
+ </Link>
+ </styled.h3>
+ <styled.span>
+ $50
+ </styled.span>
+ <p>
+ This item is really cool!
+ </p>
+ <div
+ className="buttonList"
+ >
+ <Link
+ href={
+ Object {
+ "pathname": "/admin/update",
+ "query": Object {
+ "id": "ABC123",
+ },
+ }
+ }
+ >
+ <a>
+ Edit ✏️
+ </a>
+ </Link>
+ <Apollo(Apollo(AddToCart))
+ id="ABC123"
+ />
+ <button
+ onClick={[Function]}
+ >
+ × Delete item
+ </button>
+ </div>
+</styled.div>
+`;
diff --git a/frontend/__tests__/__snapshots__/Nav.test.js.snap b/frontend/__tests__/__snapshots__/Nav.test.js.snap
new file mode 100644
index 0000000..d8520ac
--- /dev/null
+++ b/frontend/__tests__/__snapshots__/Nav.test.js.snap
@@ -0,0 +1,69 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Nav></Nav> Renders minimal nav when logged out 1`] = `
+<styled.ul>
+ <Link
+ href="/items"
+ prefetch={true}
+ >
+ <a>
+ Shop
+ </a>
+ </Link>
+ <Link
+ href="/add"
+ prefetch={true}
+ >
+ <a>
+ Sell
+ </a>
+ </Link>
+ <Link
+ href="/signup"
+ prefetch={true}
+ >
+ <a>
+ Sign In
+ </a>
+ </Link>
+</styled.ul>
+`;
+
+exports[`<Nav></Nav> renders full nav when logged in 1`] = `
+<styled.ul>
+ <Link
+ href="/items"
+ prefetch={true}
+ >
+ <a>
+ Shop
+ </a>
+ </Link>
+ <Link
+ href="/add"
+ prefetch={true}
+ >
+ <a>
+ Sell
+ </a>
+ </Link>
+ <React.Fragment>
+ <Link
+ href="/orders"
+ >
+ <a>
+ Orders
+ </a>
+ </Link>
+ <Link
+ href="/me"
+ >
+ <a>
+ My Account
+ </a>
+ </Link>
+ <Apollo(Signout) />
+ <[object Object] />
+ </React.Fragment>
+</styled.ul>
+`;
diff --git a/frontend/__tests__/__snapshots__/Pagination.test.js.snap b/frontend/__tests__/__snapshots__/Pagination.test.js.snap
new file mode 100644
index 0000000..f2abcdf
--- /dev/null
+++ b/frontend/__tests__/__snapshots__/Pagination.test.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Pagination/> displays loading message 1`] = `
+<p>
+ Loading...
+</p>
+`;
+
+exports[`<Pagination/> renders pagination for 18 items 1`] = `
+<styled.div>
+ <Link
+ href={
+ Object {
+ "pathname": "items",
+ "query": Object {
+ "page": 0,
+ },
+ }
+ }
+ prefetch={true}
+ >
+ <a
+ aria-disabled={true}
+ className="prev"
+ >
+ ←Prev
+ </a>
+ </Link>
+ <p>
+ Page
+ <strong>
+ 1
+
+ </strong>
+ of
+ <strong
+ className="totalPages"
+ >
+ 2
+ </strong>
+ </p>
+ <p>
+ <strong>
+ 18
+ </strong>
+ Items Total
+ </p>
+ <Link
+ href={
+ Object {
+ "pathname": "items",
+ "query": Object {
+ "page": 2,
+ },
+ }
+ }
+ prefetch={true}
+ >
+ <a
+ aria-disabled={false}
+ className="next"
+ >
+ Next →
+ </a>
+ </Link>
+</styled.div>
+`;
diff --git a/frontend/__tests__/formatMoney.test.js b/frontend/__tests__/formatMoney.test.js
new file mode 100644
index 0000000..c5c79b6
--- /dev/null
+++ b/frontend/__tests__/formatMoney.test.js
@@ -0,0 +1,21 @@
+import formatMoney from '../lib/formatMoney';
+
+describe('formatMoney', () => {
+ it('works with fractional dollars', () => {
+ expect(formatMoney(1)).toEqual('$0.01');
+ expect(formatMoney(10)).toEqual('$0.10');
+ });
+
+ it('leaves cents off for whole dollars', () => {
+ expect(formatMoney(5000)).toEqual('$50');
+ expect(formatMoney(100)).toEqual('$1');
+ expect(formatMoney(50000000)).toEqual('$500,000');
+ });
+
+ it('works with whole and fractional dollars', () => {
+ expect(formatMoney(5012)).toEqual('$50.12');
+ expect(formatMoney(101)).toEqual('$1.01');
+ expect(formatMoney(110)).toEqual('$1.10');
+ expect(formatMoney(54345345)).toEqual('$543,453.45');
+ });
+});
diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js
index b87d4e9..9c52f83 100644
--- a/frontend/components/AddToCart.js
+++ b/frontend/components/AddToCart.js
@@ -1,37 +1,8 @@
import { Component } from 'react';
import { graphql, compose } from 'react-apollo';
-import Transition from 'react-transition-group/Transition';
import styled from 'styled-components';
import PropTypes from 'prop-types';
-import { CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries';
-import { removeFromCartEnhancer, userEnhancer, addtoCartEnhancer } from '../enhancers/enhancers';
-
-const JumpImg = styled.img`
- border: 0 solid black;
- max-width: 100px;
- transition: all 0.5s;
- position: fixed;
- left: ${props => props.x}px;
- top: -100%;
- &.jump-entered {
- border-color: green;
- transform-origin: 0 0;
- transform: scale(0);
- top: ${props => props.y}px;
- left: ${props => props.x}px;
- }
- &.jump-entering {
- border-color: yellow;
- top: ${props => props.y}px;
- left: ${props => props.x}px;
- }
- &.jump-exited {
- border-color: red;
- }
- &.jump-exiting {
- border-color: yellow;
- }
-`;
+import { removeFromCartEnhancer, userEnhancer, addToCartEnhancer, singleItemEnhancer } from '../enhancers/enhancers';
class AddToCart extends Component {
static propTypes = {
@@ -57,12 +28,4 @@ class AddToCart extends Component {
}
}
-const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, {
- name: 'singleItemQuery',
- options: ({ id }) => ({
- variables: {
- id,
- },
- }),
-});
-export default compose(userEnhancer, addtoCartEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart);
+export default compose(userEnhancer, addToCartEnhancer)(AddToCart);
diff --git a/frontend/components/CartCount.js b/frontend/components/CartCount.js
index 245b07b..7949307 100644
--- a/frontend/components/CartCount.js
+++ b/frontend/components/CartCount.js
@@ -50,3 +50,4 @@ const CartCount = ({ count }) => (
);
export default CartCount;
+export { CartCount };
diff --git a/frontend/components/Count.js b/frontend/components/Count.js
index 800a902..7dfd33f 100644
--- a/frontend/components/Count.js
+++ b/frontend/components/Count.js
@@ -1,3 +1,4 @@
+// TODO THis is not needed
import React, { Component } from 'react'
import { graphql, gql } from 'react-apollo'
@@ -33,5 +34,4 @@ class Count extends Component {
// We export the graphQL HOC - this will fetch the data and inject it into the Count compeont via props
-export { ALL_ITEMS_QUERY }
-export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' }) (Count)
+export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' })(Count)
diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js
index 26b2c88..74a6cee 100644
--- a/frontend/components/CreateItem.js
+++ b/frontend/components/CreateItem.js
@@ -1,33 +1,41 @@
import React, { Component } from 'react';
import { graphql, gql } from 'react-apollo';
-import { ALL_ITEMS_QUERY, CREATE_ITEM_MUTATION } from '../queries';
+import { CREATE_ITEM_MUTATION } from '../queries';
import ErrorMessage from './ErrorMessage';
-import { fileEndpoint } from '../config';
import Form from './styles/Form';
+import PropTypes from 'prop-types';
+
+class CreateItem extends Component {
+ static propTypes = {
+ createItemMutation: PropTypes.func.isRequired,
+ };
-class CreateLink extends Component {
state = {
- description: 'test',
- title: 'testing title',
- image: '',
- largeImage: '',
- price: 500,
- fullPrice: 0,
+ item: {
+ title: '',
+ description: '',
+ image: '',
+ largeImage: '',
+ price: 0,
+ },
loading: false,
error: {
- message: '',
+ message: null,
},
};
- componentWillReceiveProps(nextProps) {
- console.log(nextProps);
- }
+ handleChange = e => {
+ const { name, value, type } = e.target;
+ const updatedItem = {
+ ...this.state.item,
+ [name]: type === 'number' ? parseFloat(value) : value,
+ };
+ this.setState({ item: updatedItem });
+ };
uploadFile = async e => {
this.setState({ loading: true });
-
const files = e.currentTarget.files;
-
const data = new FormData();
data.append('file', files[0]);
data.append('upload_preset', 'sickfits');
@@ -38,33 +46,23 @@ class CreateLink extends Component {
body: data,
});
const file = await res.json();
- console.log(file);
this.setState({ image: file.secure_url, largeImage: file.eager[0].secure_url, loading: false });
};
createItem = async e => {
e.preventDefault();
- // pull the values from state
- const { description, title, price, image, largeImage } = this.state;
- // create a mutation
// TODO: handle any errors
// turn loading on
this.setState({ loading: true });
try {
- console.log('About to call create item mutation');
const res = await this.props.createItemMutation({
// pass in those variables from state
variables: {
- description,
- title,
- image,
- largeImage,
- price: parseInt(price),
+ ...this.state.item,
},
});
} catch (error) {
this.setState({ error });
- console.log(error);
}
this.setState({ loading: false });
};
@@ -84,23 +82,29 @@ class CreateLink extends Component {
Title
<input
value={this.state.title}
- onChange={e => this.setState({ title: e.target.value })}
+ onChange={this.handleChange}
type="text"
+ name="title"
+ id="title"
placeholder="Title"
/>
</p>
<label>
- Price<input
+ Price
+ <input
type="number"
+ id="price"
+ name="price"
min="0"
value={this.state.price}
- onChange={e => this.setState({ price: e.target.value })}
+ onChange={this.handleChange}
/>
</label>
<textarea
+ id="description"
+ name="description"
value={this.state.description}
- onChange={e => this.setState({ description: e.target.value })}
- type="text"
+ onChange={this.handleChange}
placeholder="The desc for this item"
/>
<button disabled={this.state.loading} type="submit">
@@ -117,4 +121,6 @@ export default graphql(CREATE_ITEM_MUTATION, {
options: {
refetchQueries: ['AllItemsQuery'],
},
-})(CreateLink);
+})(CreateItem);
+
+export { CreateItem };
diff --git a/frontend/components/Item.js b/frontend/components/Item.js
index 935930d..33b3951 100644
--- a/frontend/components/Item.js
+++ b/frontend/components/Item.js
@@ -1,10 +1,8 @@
import React from 'react';
-import Title from './styles/Title';
import styled from 'styled-components';
-import slugify from 'slugify';
import { compose } from 'react-apollo';
-// import { Link } from '../routes';
import Link from 'next/link';
+import Title from './styles/Title';
import AddToCart from './AddToCart';
import formatMoney from '../lib/formatMoney';
import { removeItemMutation } from '../enhancers/enhancers';
@@ -101,4 +99,5 @@ class ItemComponent extends React.Component {
}
}
+export { ItemComponent };
export default compose(removeItemMutation)(ItemComponent);
diff --git a/frontend/components/Items.js b/frontend/components/Items.js
index 01d9786..1b0c8bb 100644
--- a/frontend/components/Items.js
+++ b/frontend/components/Items.js
@@ -18,7 +18,7 @@ const Center = styled.div`
`;
class ItemList extends React.Component {
- something() { }
+ something() {}
render() {
const { loading, error } = this.props.itemsQuery;
// 1
@@ -31,7 +31,6 @@ class ItemList extends React.Component {
console.log(this.props.itemsQuery.error);
return <div>Error</div>;
}
- console.log(this.props);
// 3
const itemsToRender = this.props.itemsQuery.items;
diff --git a/frontend/components/Nav.js b/frontend/components/Nav.js
index aa0300b..3d7f0f6 100644
--- a/frontend/components/Nav.js
+++ b/frontend/components/Nav.js
@@ -1,8 +1,9 @@
+import React, { Fragment } from 'react';
import Link from 'next/link';
import styled from 'styled-components';
-import { Fragment } from 'react';
-import { userEnhancer } from '../enhancers/enhancers';
import { compose } from 'react-apollo';
+import PropTypes from 'prop-types';
+import { userEnhancer } from '../enhancers/enhancers';
import CartCount from './CartCount';
import Signout from './Signout';
import { UIContext } from './UIContext';
@@ -58,6 +59,9 @@ const StyledUl = styled.ul`
`;
class Nav extends React.Component {
+ static propTypes = {
+ currentUser: PropTypes.object.isRequired,
+ };
componentDidMount() {
this.props.currentUser.refetch();
}
@@ -106,3 +110,4 @@ class Nav extends React.Component {
}
export default compose(userEnhancer)(Nav);
+export { Nav };
diff --git a/frontend/components/Pagination.js b/frontend/components/Pagination.js
index dab7084..abc22d0 100644
--- a/frontend/components/Pagination.js
+++ b/frontend/components/Pagination.js
@@ -2,6 +2,7 @@ import React from 'react';
import { compose } 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';
@@ -30,12 +31,11 @@ const PaginationStyles = styled.div`
`;
const Pagination = props => {
- if (props.loading) return <p>Loading Item...</p>;
- const { aggregate, pageInfo } = props.itemsQuery.itemsConnection;
+ if (props.loading) return <p>Loading...</p>;
+ const { aggregate } = props.itemsQuery.itemsConnection;
const { page } = props;
-
const pages = Math.ceil(aggregate.count / perPage);
- console.log({ pageInfo });
+
return (
<PaginationStyles>
<Link
@@ -45,10 +45,12 @@ const Pagination = props => {
query: { page: page - 1 },
}}
>
- <a aria-disabled={page <= 1}>←Prev</a>
+ <a className="prev" aria-disabled={page <= 1}>
+ ←Prev
+ </a>
</Link>
<p>
- Page <strong>{page} </strong> of <strong>{pages} </strong>
+ Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong>
</p>
<p>
<strong>{aggregate.count}</strong> Items Total
@@ -60,12 +62,24 @@ const Pagination = props => {
query: { page: page + 1 },
}}
>
- <a aria-disabled={page >= pages}>Next →</a>
+ <a className="next" aria-disabled={page >= pages}>
+ Next →
+ </a>
</Link>
</PaginationStyles>
);
};
-const ComponentWithMutations = compose(itemEnhancer)(Pagination);
+Pagination.propTypes = {
+ itemsQuery: PropTypes.shape({
+ itemsConnection: PropTypes.shape({
+ aggregate: PropTypes.shape({
+ count: PropTypes.number.isRequired,
+ }),
+ }),
+ }).isRequired,
+ page: PropTypes.number.isRequired,
+};
-export default ComponentWithMutations;
+export default compose(itemEnhancer)(Pagination);
+export { Pagination };
diff --git a/frontend/components/Search.js b/frontend/components/Search.js
index 33017dd..d30789c 100644
--- a/frontend/components/Search.js
+++ b/frontend/components/Search.js
@@ -1,15 +1,15 @@
import Downshift from 'downshift';
import { graphql, compose } from 'react-apollo';
-import slugify from 'slugify';
import styled from 'styled-components';
import { SEARCH_ITEMS_QUERY } from '../queries';
import { Router } from '../routes';
function routeToItem(item) {
- Router.pushRoute('item', {
- slug: slugify(item.title),
- itemId: item.id,
- });
+ console.log('TODO: UPdate routeToItem function');
+ // Router.pushRoute('item', {
+ // slug: slugify(item.title),
+ // itemId: item.id,
+ // });
}
const DropDown = styled.div`
diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js
index add1707..f591f55 100644
--- a/frontend/components/SingleItem.js
+++ b/frontend/components/SingleItem.js
@@ -1,13 +1,11 @@
-import { graphql, compose } from 'react-apollo';
-import { SINGLE_ITEM_QUERY } from '../queries';
+import { compose } from 'react-apollo';
import { singleItemEnhancer } from '../enhancers/enhancers';
-const SingleItem = props => {
- console.log(props);
- if (props.loading) return <p>Loading...</p>;
- if (props.error) return <p>Error...</p>;
- const item = props.findItem.items[0];
- console.log(item);
+const SingleItem = ({ findItem: { error, loading, items } }) => {
+ if (loading) return <p>Loading...</p>;
+ if (error) return <p>Error...</p>;
+ const item = items[0];
+
return (
<div>
<img src={item.largeImage || item.image} alt={item.title} />
@@ -17,6 +15,4 @@ const SingleItem = props => {
);
};
-const ComponentWithMutations = compose(singleItemEnhancer)(SingleItem);
-
-export default ComponentWithMutations;
+export default compose(singleItemEnhancer)(SingleItem);
diff --git a/frontend/components/styles/Form.js b/frontend/components/styles/Form.js
index db0eeb4..09085ad 100644
--- a/frontend/components/styles/Form.js
+++ b/frontend/components/styles/Form.js
@@ -37,4 +37,7 @@ const Form = styled.form`
padding: 1rem 2rem;
}
`;
+
+Form.displayName = 'Form';
+
export default Form;
diff --git a/frontend/enhancers/enhancers.js b/frontend/enhancers/enhancers.js
index b96a747..2651b6c 100644
--- a/frontend/enhancers/enhancers.js
+++ b/frontend/enhancers/enhancers.js
@@ -61,7 +61,7 @@ export const removeFromCartEnhancer = graphql(REMOVE_FROM_CART_MUTATION, {
},
});
-export const addtoCartEnhancer = graphql(ADD_TO_CART_MUTATION, {
+export const addToCartEnhancer = graphql(ADD_TO_CART_MUTATION, {
name: 'addToCart',
options: {
update: (proxy, payload) => {
diff --git a/frontend/lib/formatMoney.js b/frontend/lib/formatMoney.js
index edddf48..301f01f 100644
--- a/frontend/lib/formatMoney.js
+++ b/frontend/lib/formatMoney.js
@@ -1,3 +1,11 @@
-export default function(amount) {
- return '$' + (amount / 100).toLocaleString();
+export default function (amount) {
+ const options = {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 2,
+ };
+ // if its a whole, dollar amount, leave off the .00
+ if (amount % 100 === 0) options.minimumFractionDigits = 0;
+ const formatter = new Intl.NumberFormat('en-US', options);
+ return formatter.format(amount / 100);
}
diff --git a/frontend/package.json b/frontend/package.json
index 9de6f9a..f032a04 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -65,11 +65,29 @@
"development": {
"presets": [
"next/babel"
+ ],
+ "plugins": [
+ [
+ "styled-components",
+ {
+ "ssr": true,
+ "displayName": true
+ }
+ ]
]
},
"production": {
"presets": [
"next/babel"
+ ],
+ "plugins": [
+ [
+ "styled-components",
+ {
+ "ssr": true,
+ "displayName": false
+ }
+ ]
]
},
"test": {
@@ -86,4 +104,4 @@
}
}
}
-}
+} \ No newline at end of file