summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWes Bos <wesbos@gmail.com>2018-08-21 12:16:26 -0400
committerWes Bos <wesbos@gmail.com>2018-08-21 12:16:26 -0400
commit3065f07ba8f26c7ccfe440632655fb809dc3ac03 (patch)
treec98777028240d1021b63fbc773af24cb4222de71
parentc3bbebfcfeed7a9b8c7470d7b48e12046d834e59 (diff)
47
-rw-r--r--finished-application/backend/prisma/datamodel.graphql2
-rw-r--r--finished-application/frontend/components/Search.js46
-rw-r--r--finished-application/frontend/components/styles/DropDown.js47
-rw-r--r--sick-fits/frontend/components/styles/DropDown.js47
-rwxr-xr-xstepped-solutions/44/frontend/components/RemoveFromCart.js72
-rwxr-xr-xstepped-solutions/45/frontend/components/CartCount.js60
-rwxr-xr-xstepped-solutions/45/frontend/components/Nav.js49
-rwxr-xr-xstepped-solutions/46/backend/datamodel.graphql37
-rwxr-xr-xstepped-solutions/46/backend/src/generated/prisma.graphql1142
-rwxr-xr-xstepped-solutions/46/frontend/components/CartItem.js53
10 files changed, 1509 insertions, 46 deletions
diff --git a/finished-application/backend/prisma/datamodel.graphql b/finished-application/backend/prisma/datamodel.graphql
index d652dc4..8b19125 100644
--- a/finished-application/backend/prisma/datamodel.graphql
+++ b/finished-application/backend/prisma/datamodel.graphql
@@ -24,7 +24,7 @@ type User {
type CartItem {
id: ID! @unique
quantity: Int! @default(value: 1)
- item: Item # Relationship to an Item
+ item: Item! # Relationship to an Item
user: User! # Relationship to a user
createdAt: DateTime!
updatedAt: DateTime!
diff --git a/finished-application/frontend/components/Search.js b/finished-application/frontend/components/Search.js
index a972db2..dc1bfea 100644
--- a/finished-application/frontend/components/Search.js
+++ b/finished-application/frontend/components/Search.js
@@ -3,8 +3,8 @@ import Downshift 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!) {
@@ -24,50 +24,6 @@ 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: [],
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/sick-fits/frontend/components/styles/DropDown.js b/sick-fits/frontend/components/styles/DropDown.js
new file mode 100644
index 0000000..df86aee
--- /dev/null
+++ b/sick-fits/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/stepped-solutions/44/frontend/components/RemoveFromCart.js b/stepped-solutions/44/frontend/components/RemoveFromCart.js
new file mode 100755
index 0000000..065235a
--- /dev/null
+++ b/stepped-solutions/44/frontend/components/RemoveFromCart.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { Mutation } from 'react-apollo';
+import styled from 'styled-components';
+import PropTypes from 'prop-types';
+import gql from 'graphql-tag';
+import { CURRENT_USER_QUERY } from './User';
+
+const REMOVE_FROM_CART_MUTATION = gql`
+ mutation removeFromCart($id: ID!) {
+ removeFromCart(id: $id) {
+ id
+ }
+ }
+`;
+
+const BigButton = styled.button`
+ font-size: 3rem;
+ background: none;
+ border: 0;
+ &:hover {
+ color: ${props => props.theme.red};
+ cursor: pointer;
+ }
+`;
+
+class RemoveFromCart extends 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) => {
+ console.log('Running remove from cart update fn');
+ // 1. first read the cache
+ const data = cache.readQuery({ query: CURRENT_USER_QUERY });
+ console.log(data);
+ // 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, error }) => (
+ <BigButton
+ disabled={loading}
+ onClick={() => {
+ removeFromCart().catch(err => alert(err.message));
+ }}
+ title="Delete Item"
+ >
+ &times;
+ </BigButton>
+ )}
+ </Mutation>
+ );
+ }
+}
+
+export default RemoveFromCart;
diff --git a/stepped-solutions/45/frontend/components/CartCount.js b/stepped-solutions/45/frontend/components/CartCount.js
new file mode 100755
index 0000000..c202d74
--- /dev/null
+++ b/stepped-solutions/45/frontend/components/CartCount.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { TransitionGroup, CSSTransition } from 'react-transition-group';
+import styled from 'styled-components';
+
+const AnimationStyles = styled.span`
+ position: relative;
+ .count {
+ display: block;
+ position: relative;
+ transition: all 0.4s;
+ backface-visibility: hidden;
+ }
+ /* Intial State of the entered Dot */
+ .count-enter {
+ 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: 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>
+ <CSSTransition
+ unmountOnExit
+ className="count"
+ classNames="count"
+ key={count}
+ timeout={{ enter: 400, exit: 400 }}
+ >
+ <Dot>{count}</Dot>
+ </CSSTransition>
+ </TransitionGroup>
+ </AnimationStyles>
+);
+
+export default CartCount;
diff --git a/stepped-solutions/45/frontend/components/Nav.js b/stepped-solutions/45/frontend/components/Nav.js
new file mode 100755
index 0000000..594d048
--- /dev/null
+++ b/stepped-solutions/45/frontend/components/Nav.js
@@ -0,0 +1,49 @@
+import Link from 'next/link';
+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';
+
+const Nav = () => (
+ <User>
+ {({ data: { me } }) => (
+ <NavStyles>
+ <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>
+
+ )}
+ </NavStyles>
+ )}
+ </User>
+);
+
+export default Nav;
diff --git a/stepped-solutions/46/backend/datamodel.graphql b/stepped-solutions/46/backend/datamodel.graphql
new file mode 100755
index 0000000..9786a4b
--- /dev/null
+++ b/stepped-solutions/46/backend/datamodel.graphql
@@ -0,0 +1,37 @@
+enum Permission {
+ ADMIN
+ USER
+ ITEMCREATE
+ ITEMUPDATE
+ ITEMDELETE
+ PERMISSIONUPDATE
+}
+
+type User {
+ id: ID! @unique
+ name: String!
+ email: String! @unique
+ password: String!
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: [Permission]
+ cart: [CartItem!]!
+}
+
+type Item {
+ id: ID! @unique
+ title: String!
+ description: String!
+ image: String
+ largeImage: String
+ price: Int!
+ user: User!
+}
+
+
+type CartItem {
+ id: ID! @unique
+ quantity: Int! @default(value: 1)
+ item: Item # relationship to Item
+ user: User! # relationship to User
+}
diff --git a/stepped-solutions/46/backend/src/generated/prisma.graphql b/stepped-solutions/46/backend/src/generated/prisma.graphql
new file mode 100755
index 0000000..b9dbc95
--- /dev/null
+++ b/stepped-solutions/46/backend/src/generated/prisma.graphql
@@ -0,0 +1,1142 @@
+# source: https://us1.prisma.sh/wesbos/siccccccccck-fits/dev
+# timestamp: Tue Aug 21 2018 10:13:27 GMT-0400 (EDT)
+
+type AggregateCartItem {
+ count: Int!
+}
+
+type AggregateItem {
+ count: Int!
+}
+
+type AggregateUser {
+ count: Int!
+}
+
+type BatchPayload {
+ """The number of nodes that have been affected by the Batch operation."""
+ count: Long!
+}
+
+type CartItem implements Node {
+ id: ID!
+ quantity: Int!
+ item(where: ItemWhereInput): Item
+ user(where: UserWhereInput): User!
+}
+
+"""A connection to a list of items."""
+type CartItemConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [CartItemEdge]!
+ aggregate: AggregateCartItem!
+}
+
+input CartItemCreateInput {
+ quantity: Int
+ item: ItemCreateOneInput
+ user: UserCreateOneWithoutCartInput!
+}
+
+input CartItemCreateManyWithoutUserInput {
+ create: [CartItemCreateWithoutUserInput!]
+ connect: [CartItemWhereUniqueInput!]
+}
+
+input CartItemCreateWithoutUserInput {
+ quantity: Int
+ item: ItemCreateOneInput
+}
+
+"""An edge in a connection."""
+type CartItemEdge {
+ """The item at the end of the edge."""
+ node: CartItem!
+
+ """A cursor for use in pagination."""
+ cursor: String!
+}
+
+enum CartItemOrderByInput {
+ id_ASC
+ id_DESC
+ quantity_ASC
+ quantity_DESC
+ updatedAt_ASC
+ updatedAt_DESC
+ createdAt_ASC
+ createdAt_DESC
+}
+
+type CartItemPreviousValues {
+ id: ID!
+ quantity: Int!
+}
+
+type CartItemSubscriptionPayload {
+ mutation: MutationType!
+ node: CartItem
+ updatedFields: [String!]
+ previousValues: CartItemPreviousValues
+}
+
+input CartItemSubscriptionWhereInput {
+ """Logical AND on all given filters."""
+ AND: [CartItemSubscriptionWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [CartItemSubscriptionWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [CartItemSubscriptionWhereInput!]
+
+ """
+ The subscription event gets dispatched when it's listed in mutation_in
+ """
+ mutation_in: [MutationType!]
+
+ """
+ The subscription event gets only dispatched when one of the updated fields names is included in this list
+ """
+ updatedFields_contains: String
+
+ """
+ The subscription event gets only dispatched when all of the field names included in this list have been updated
+ """
+ updatedFields_contains_every: [String!]
+
+ """
+ The subscription event gets only dispatched when some of the field names included in this list have been updated
+ """
+ updatedFields_contains_some: [String!]
+ node: CartItemWhereInput
+}
+
+input CartItemUpdateInput {
+ quantity: Int
+ item: ItemUpdateOneInput
+ user: UserUpdateOneWithoutCartInput
+}
+
+input CartItemUpdateManyWithoutUserInput {
+ create: [CartItemCreateWithoutUserInput!]
+ connect: [CartItemWhereUniqueInput!]
+ disconnect: [CartItemWhereUniqueInput!]
+ delete: [CartItemWhereUniqueInput!]
+ update: [CartItemUpdateWithWhereUniqueWithoutUserInput!]
+ upsert: [CartItemUpsertWithWhereUniqueWithoutUserInput!]
+}
+
+input CartItemUpdateWithoutUserDataInput {
+ quantity: Int
+ item: ItemUpdateOneInput
+}
+
+input CartItemUpdateWithWhereUniqueWithoutUserInput {
+ where: CartItemWhereUniqueInput!
+ data: CartItemUpdateWithoutUserDataInput!
+}
+
+input CartItemUpsertWithWhereUniqueWithoutUserInput {
+ where: CartItemWhereUniqueInput!
+ update: CartItemUpdateWithoutUserDataInput!
+ create: CartItemCreateWithoutUserInput!
+}
+
+input CartItemWhereInput {
+ """Logical AND on all given filters."""
+ AND: [CartItemWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [CartItemWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [CartItemWhereInput!]
+ id: ID
+
+ """All values that are not equal to given value."""
+ id_not: ID
+
+ """All values that are contained in given list."""
+ id_in: [ID!]
+
+ """All values that are not contained in given list."""
+ id_not_in: [ID!]
+
+ """All values less than the given value."""
+ id_lt: ID
+
+ """All values less than or equal the given value."""
+ id_lte: ID
+
+ """All values greater than the given value."""
+ id_gt: ID
+
+ """All values greater than or equal the given value."""
+ id_gte: ID
+
+ """All values containing the given string."""
+ id_contains: ID
+
+ """All values not containing the given string."""
+ id_not_contains: ID
+
+ """All values starting with the given string."""
+ id_starts_with: ID
+
+ """All values not starting with the given string."""
+ id_not_starts_with: ID
+
+ """All values ending with the given string."""
+ id_ends_with: ID
+
+ """All values not ending with the given string."""
+ id_not_ends_with: ID
+ quantity: Int
+
+ """All values that are not equal to given value."""
+ quantity_not: Int
+
+ """All values that are contained in given list."""
+ quantity_in: [Int!]
+
+ """All values that are not contained in given list."""
+ quantity_not_in: [Int!]
+
+ """All values less than the given value."""
+ quantity_lt: Int
+
+ """All values less than or equal the given value."""
+ quantity_lte: Int
+
+ """All values greater than the given value."""
+ quantity_gt: Int
+
+ """All values greater than or equal the given value."""
+ quantity_gte: Int
+ item: ItemWhereInput
+ user: UserWhereInput
+}
+
+input CartItemWhereUniqueInput {
+ id: ID
+}
+
+type Item implements Node {
+ id: ID!
+ title: String!
+ description: String!
+ image: String
+ largeImage: String
+ price: Int!
+ user(where: UserWhereInput): User!
+}
+
+"""A connection to a list of items."""
+type ItemConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [ItemEdge]!
+ aggregate: AggregateItem!
+}
+
+input ItemCreateInput {
+ title: String!
+ description: String!
+ image: String
+ largeImage: String
+ price: Int!
+ user: UserCreateOneInput!
+}
+
+input ItemCreateOneInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+}
+
+"""An edge in a connection."""
+type ItemEdge {
+ """The item at the end of the edge."""
+ node: Item!
+
+ """A cursor for use in pagination."""
+ cursor: String!
+}
+
+enum ItemOrderByInput {
+ id_ASC
+ id_DESC
+ title_ASC
+ title_DESC
+ description_ASC
+ description_DESC
+ image_ASC
+ image_DESC
+ largeImage_ASC
+ largeImage_DESC
+ price_ASC
+ price_DESC
+ updatedAt_ASC
+ updatedAt_DESC
+ createdAt_ASC
+ createdAt_DESC
+}
+
+type ItemPreviousValues {
+ id: ID!
+ title: String!
+ description: String!
+ image: String
+ largeImage: String
+ price: Int!
+}
+
+type ItemSubscriptionPayload {
+ mutation: MutationType!
+ node: Item
+ updatedFields: [String!]
+ previousValues: ItemPreviousValues
+}
+
+input ItemSubscriptionWhereInput {
+ """Logical AND on all given filters."""
+ AND: [ItemSubscriptionWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [ItemSubscriptionWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [ItemSubscriptionWhereInput!]
+
+ """
+ The subscription event gets dispatched when it's listed in mutation_in
+ """
+ mutation_in: [MutationType!]
+
+ """
+ The subscription event gets only dispatched when one of the updated fields names is included in this list
+ """
+ updatedFields_contains: String
+
+ """
+ The subscription event gets only dispatched when all of the field names included in this list have been updated
+ """
+ updatedFields_contains_every: [String!]
+
+ """
+ The subscription event gets only dispatched when some of the field names included in this list have been updated
+ """
+ updatedFields_contains_some: [String!]
+ node: ItemWhereInput
+}
+
+input ItemUpdateDataInput {
+ title: String
+ description: String
+ image: String
+ largeImage: String
+ price: Int
+ user: UserUpdateOneInput
+}
+
+input ItemUpdateInput {
+ title: String
+ description: String
+ image: String
+ largeImage: String
+ price: Int
+ user: UserUpdateOneInput
+}
+
+input ItemUpdateOneInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ disconnect: Boolean
+ delete: Boolean
+ update: ItemUpdateDataInput
+ upsert: ItemUpsertNestedInput
+}
+
+input ItemUpsertNestedInput {
+ update: ItemUpdateDataInput!
+ create: ItemCreateInput!
+}
+
+input ItemWhereInput {
+ """Logical AND on all given filters."""
+ AND: [ItemWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [ItemWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [ItemWhereInput!]
+ id: ID
+
+ """All values that are not equal to given value."""
+ id_not: ID
+
+ """All values that are contained in given list."""
+ id_in: [ID!]
+
+ """All values that are not contained in given list."""
+ id_not_in: [ID!]
+
+ """All values less than the given value."""
+ id_lt: ID
+
+ """All values less than or equal the given value."""
+ id_lte: ID
+
+ """All values greater than the given value."""
+ id_gt: ID
+
+ """All values greater than or equal the given value."""
+ id_gte: ID
+
+ """All values containing the given string."""
+ id_contains: ID
+
+ """All values not containing the given string."""
+ id_not_contains: ID
+
+ """All values starting with the given string."""
+ id_starts_with: ID
+
+ """All values not starting with the given string."""
+ id_not_starts_with: ID
+
+ """All values ending with the given string."""
+ id_ends_with: ID
+
+ """All values not ending with the given string."""
+ id_not_ends_with: ID
+ title: String
+
+ """All values that are not equal to given value."""
+ title_not: String
+
+ """All values that are contained in given list."""
+ title_in: [String!]
+
+ """All values that are not contained in given list."""
+ title_not_in: [String!]
+
+ """All values less than the given value."""
+ title_lt: String
+
+ """All values less than or equal the given value."""
+ title_lte: String
+
+ """All values greater than the given value."""
+ title_gt: String
+
+ """All values greater than or equal the given value."""
+ title_gte: String
+
+ """All values containing the given string."""
+ title_contains: String
+
+ """All values not containing the given string."""
+ title_not_contains: String
+
+ """All values starting with the given string."""
+ title_starts_with: String
+
+ """All values not starting with the given string."""
+ title_not_starts_with: String
+
+ """All values ending with the given string."""
+ title_ends_with: String
+
+ """All values not ending with the given string."""
+ title_not_ends_with: String
+ description: String
+
+ """All values that are not equal to given value."""
+ description_not: String
+
+ """All values that are contained in given list."""
+ description_in: [String!]
+
+ """All values that are not contained in given list."""
+ description_not_in: [String!]
+
+ """All values less than the given value."""
+ description_lt: String
+
+ """All values less than or equal the given value."""
+ description_lte: String
+
+ """All values greater than the given value."""
+ description_gt: String
+
+ """All values greater than or equal the given value."""
+ description_gte: String
+
+ """All values containing the given string."""
+ description_contains: String
+
+ """All values not containing the given string."""
+ description_not_contains: String
+
+ """All values starting with the given string."""
+ description_starts_with: String
+
+ """All values not starting with the given string."""
+ description_not_starts_with: String
+
+ """All values ending with the given string."""
+ description_ends_with: String
+
+ """All values not ending with the given string."""
+ description_not_ends_with: String
+ image: String
+
+ """All values that are not equal to given value."""
+ image_not: String
+
+ """All values that are contained in given list."""
+ image_in: [String!]
+
+ """All values that are not contained in given list."""
+ image_not_in: [String!]
+
+ """All values less than the given value."""
+ image_lt: String
+
+ """All values less than or equal the given value."""
+ image_lte: String
+
+ """All values greater than the given value."""
+ image_gt: String
+
+ """All values greater than or equal the given value."""
+ image_gte: String
+
+ """All values containing the given string."""
+ image_contains: String
+
+ """All values not containing the given string."""
+ image_not_contains: String
+
+ """All values starting with the given string."""
+ image_starts_with: String
+
+ """All values not starting with the given string."""
+ image_not_starts_with: String
+
+ """All values ending with the given string."""
+ image_ends_with: String
+
+ """All values not ending with the given string."""
+ image_not_ends_with: String
+ largeImage: String
+
+ """All values that are not equal to given value."""
+ largeImage_not: String
+
+ """All values that are contained in given list."""
+ largeImage_in: [String!]
+
+ """All values that are not contained in given list."""
+ largeImage_not_in: [String!]
+
+ """All values less than the given value."""
+ largeImage_lt: String
+
+ """All values less than or equal the given value."""
+ largeImage_lte: String
+
+ """All values greater than the given value."""
+ largeImage_gt: String
+
+ """All values greater than or equal the given value."""
+ largeImage_gte: String
+
+ """All values containing the given string."""
+ largeImage_contains: String
+
+ """All values not containing the given string."""
+ largeImage_not_contains: String
+
+ """All values starting with the given string."""
+ largeImage_starts_with: String
+
+ """All values not starting with the given string."""
+ largeImage_not_starts_with: String
+
+ """All values ending with the given string."""
+ largeImage_ends_with: String
+
+ """All values not ending with the given string."""
+ largeImage_not_ends_with: String
+ price: Int
+
+ """All values that are not equal to given value."""
+ price_not: Int
+
+ """All values that are contained in given list."""
+ price_in: [Int!]
+
+ """All values that are not contained in given list."""
+ price_not_in: [Int!]
+
+ """All values less than the given value."""
+ price_lt: Int
+
+ """All values less than or equal the given value."""
+ price_lte: Int
+
+ """All values greater than the given value."""
+ price_gt: Int
+
+ """All values greater than or equal the given value."""
+ price_gte: Int
+ user: UserWhereInput
+}
+
+input ItemWhereUniqueInput {
+ id: ID
+}
+
+"""
+The `Long` scalar type represents non-fractional signed whole numeric values.
+Long can represent values between -(2^63) and 2^63 - 1.
+"""
+scalar Long
+
+type Mutation {
+ createCartItem(data: CartItemCreateInput!): CartItem!
+ createItem(data: ItemCreateInput!): Item!
+ createUser(data: UserCreateInput!): User!
+ updateCartItem(data: CartItemUpdateInput!, where: CartItemWhereUniqueInput!): CartItem
+ updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item
+ updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
+ deleteCartItem(where: CartItemWhereUniqueInput!): CartItem
+ deleteItem(where: ItemWhereUniqueInput!): Item
+ deleteUser(where: UserWhereUniqueInput!): User
+ upsertCartItem(where: CartItemWhereUniqueInput!, create: CartItemCreateInput!, update: CartItemUpdateInput!): CartItem!
+ upsertItem(where: ItemWhereUniqueInput!, create: ItemCreateInput!, update: ItemUpdateInput!): Item!
+ upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User!
+ updateManyCartItems(data: CartItemUpdateInput!, where: CartItemWhereInput): BatchPayload!
+ updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput): BatchPayload!
+ updateManyUsers(data: UserUpdateInput!, where: UserWhereInput): BatchPayload!
+ deleteManyCartItems(where: CartItemWhereInput): BatchPayload!
+ deleteManyItems(where: ItemWhereInput): BatchPayload!
+ deleteManyUsers(where: UserWhereInput): BatchPayload!
+}
+
+enum MutationType {
+ CREATED
+ UPDATED
+ DELETED
+}
+
+"""An object with an ID"""
+interface Node {
+ """The id of the object."""
+ id: ID!
+}
+
+"""Information about pagination in a connection."""
+type PageInfo {
+ """When paginating forwards, are there more items?"""
+ hasNextPage: Boolean!
+
+ """When paginating backwards, are there more items?"""
+ hasPreviousPage: Boolean!
+
+ """When paginating backwards, the cursor to continue."""
+ startCursor: String
+
+ """When paginating forwards, the cursor to continue."""
+ endCursor: String
+}
+
+enum Permission {
+ ADMIN
+ USER
+ ITEMCREATE
+ ITEMUPDATE
+ ITEMDELETE
+ PERMISSIONUPDATE
+}
+
+type Query {
+ cartItems(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem]!
+ items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Item]!
+ users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
+ cartItem(where: CartItemWhereUniqueInput!): CartItem
+ item(where: ItemWhereUniqueInput!): Item
+ user(where: UserWhereUniqueInput!): User
+ cartItemsConnection(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): CartItemConnection!
+ itemsConnection(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): ItemConnection!
+ usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection!
+
+ """Fetches an object given its ID"""
+ node(
+ """The ID of an object"""
+ id: ID!
+ ): Node
+}
+
+type Subscription {
+ cartItem(where: CartItemSubscriptionWhereInput): CartItemSubscriptionPayload
+ item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload
+ user(where: UserSubscriptionWhereInput): UserSubscriptionPayload
+}
+
+type User implements Node {
+ id: ID!
+ name: String!
+ email: String!
+ password: String!
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: [Permission!]!
+ cart(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem!]
+}
+
+"""A connection to a list of items."""
+type UserConnection {
+ """Information to aid in pagination."""
+ pageInfo: PageInfo!
+
+ """A list of edges."""
+ edges: [UserEdge]!
+ aggregate: AggregateUser!
+}
+
+input UserCreateInput {
+ name: String!
+ email: String!
+ password: String!
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: UserCreatepermissionsInput
+ cart: CartItemCreateManyWithoutUserInput
+}
+
+input UserCreateOneInput {
+ create: UserCreateInput
+ connect: UserWhereUniqueInput
+}
+
+input UserCreateOneWithoutCartInput {
+ create: UserCreateWithoutCartInput
+ connect: UserWhereUniqueInput
+}
+
+input UserCreatepermissionsInput {
+ set: [Permission!]
+}
+
+input UserCreateWithoutCartInput {
+ name: String!
+ email: String!
+ password: String!
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: UserCreatepermissionsInput
+}
+
+"""An edge in a connection."""
+type UserEdge {
+ """The item at the end of the edge."""
+ node: User!
+
+ """A cursor for use in pagination."""
+ cursor: String!
+}
+
+enum UserOrderByInput {
+ id_ASC
+ id_DESC
+ name_ASC
+ name_DESC
+ email_ASC
+ email_DESC
+ password_ASC
+ password_DESC
+ resetToken_ASC
+ resetToken_DESC
+ resetTokenExpiry_ASC
+ resetTokenExpiry_DESC
+ updatedAt_ASC
+ updatedAt_DESC
+ createdAt_ASC
+ createdAt_DESC
+}
+
+type UserPreviousValues {
+ id: ID!
+ name: String!
+ email: String!
+ password: String!
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: [Permission!]!
+}
+
+type UserSubscriptionPayload {
+ mutation: MutationType!
+ node: User
+ updatedFields: [String!]
+ previousValues: UserPreviousValues
+}
+
+input UserSubscriptionWhereInput {
+ """Logical AND on all given filters."""
+ AND: [UserSubscriptionWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [UserSubscriptionWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [UserSubscriptionWhereInput!]
+
+ """
+ The subscription event gets dispatched when it's listed in mutation_in
+ """
+ mutation_in: [MutationType!]
+
+ """
+ The subscription event gets only dispatched when one of the updated fields names is included in this list
+ """
+ updatedFields_contains: String
+
+ """
+ The subscription event gets only dispatched when all of the field names included in this list have been updated
+ """
+ updatedFields_contains_every: [String!]
+
+ """
+ The subscription event gets only dispatched when some of the field names included in this list have been updated
+ """
+ updatedFields_contains_some: [String!]
+ node: UserWhereInput
+}
+
+input UserUpdateDataInput {
+ name: String
+ email: String
+ password: String
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: UserUpdatepermissionsInput
+ cart: CartItemUpdateManyWithoutUserInput
+}
+
+input UserUpdateInput {
+ name: String
+ email: String
+ password: String
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: UserUpdatepermissionsInput
+ cart: CartItemUpdateManyWithoutUserInput
+}
+
+input UserUpdateOneInput {
+ create: UserCreateInput
+ connect: UserWhereUniqueInput
+ delete: Boolean
+ update: UserUpdateDataInput
+ upsert: UserUpsertNestedInput
+}
+
+input UserUpdateOneWithoutCartInput {
+ create: UserCreateWithoutCartInput
+ connect: UserWhereUniqueInput
+ delete: Boolean
+ update: UserUpdateWithoutCartDataInput
+ upsert: UserUpsertWithoutCartInput
+}
+
+input UserUpdatepermissionsInput {
+ set: [Permission!]
+}
+
+input UserUpdateWithoutCartDataInput {
+ name: String
+ email: String
+ password: String
+ resetToken: String
+ resetTokenExpiry: String
+ permissions: UserUpdatepermissionsInput
+}
+
+input UserUpsertNestedInput {
+ update: UserUpdateDataInput!
+ create: UserCreateInput!
+}
+
+input UserUpsertWithoutCartInput {
+ update: UserUpdateWithoutCartDataInput!
+ create: UserCreateWithoutCartInput!
+}
+
+input UserWhereInput {
+ """Logical AND on all given filters."""
+ AND: [UserWhereInput!]
+
+ """Logical OR on all given filters."""
+ OR: [UserWhereInput!]
+
+ """Logical NOT on all given filters combined by AND."""
+ NOT: [UserWhereInput!]
+ id: ID
+
+ """All values that are not equal to given value."""
+ id_not: ID
+
+ """All values that are contained in given list."""
+ id_in: [ID!]
+
+ """All values that are not contained in given list."""
+ id_not_in: [ID!]
+
+ """All values less than the given value."""
+ id_lt: ID
+
+ """All values less than or equal the given value."""
+ id_lte: ID
+
+ """All values greater than the given value."""
+ id_gt: ID
+
+ """All values greater than or equal the given value."""
+ id_gte: ID
+
+ """All values containing the given string."""
+ id_contains: ID
+
+ """All values not containing the given string."""
+ id_not_contains: ID
+
+ """All values starting with the given string."""
+ id_starts_with: ID
+
+ """All values not starting with the given string."""
+ id_not_starts_with: ID
+
+ """All values ending with the given string."""
+ id_ends_with: ID
+
+ """All values not ending with the given string."""
+ id_not_ends_with: ID
+ name: String
+
+ """All values that are not equal to given value."""
+ name_not: String
+
+ """All values that are contained in given list."""
+ name_in: [String!]
+
+ """All values that are not contained in given list."""
+ name_not_in: [String!]
+
+ """All values less than the given value."""
+ name_lt: String
+
+ """All values less than or equal the given value."""
+ name_lte: String
+
+ """All values greater than the given value."""
+ name_gt: String
+
+ """All values greater than or equal the given value."""
+ name_gte: String
+
+ """All values containing the given string."""
+ name_contains: String
+
+ """All values not containing the given string."""
+ name_not_contains: String
+
+ """All values starting with the given string."""
+ name_starts_with: String
+
+ """All values not starting with the given string."""
+ name_not_starts_with: String
+
+ """All values ending with the given string."""
+ name_ends_with: String
+
+ """All values not ending with the given string."""
+ name_not_ends_with: String
+ email: String
+
+ """All values that are not equal to given value."""
+ email_not: String
+
+ """All values that are contained in given list."""
+ email_in: [String!]
+
+ """All values that are not contained in given list."""
+ email_not_in: [String!]
+
+ """All values less than the given value."""
+ email_lt: String
+
+ """All values less than or equal the given value."""
+ email_lte: String
+
+ """All values greater than the given value."""
+ email_gt: String
+
+ """All values greater than or equal the given value."""
+ email_gte: String
+
+ """All values containing the given string."""
+ email_contains: String
+
+ """All values not containing the given string."""
+ email_not_contains: String
+
+ """All values starting with the given string."""
+ email_starts_with: String
+
+ """All values not starting with the given string."""
+ email_not_starts_with: String
+
+ """All values ending with the given string."""
+ email_ends_with: String
+
+ """All values not ending with the given string."""
+ email_not_ends_with: String
+ password: String
+
+ """All values that are not equal to given value."""
+ password_not: String
+
+ """All values that are contained in given list."""
+ password_in: [String!]
+
+ """All values that are not contained in given list."""
+ password_not_in: [String!]
+
+ """All values less than the given value."""
+ password_lt: String
+
+ """All values less than or equal the given value."""
+ password_lte: String
+
+ """All values greater than the given value."""
+ password_gt: String
+
+ """All values greater than or equal the given value."""
+ password_gte: String
+
+ """All values containing the given string."""
+ password_contains: String
+
+ """All values not containing the given string."""
+ password_not_contains: String
+
+ """All values starting with the given string."""
+ password_starts_with: String
+
+ """All values not starting with the given string."""
+ password_not_starts_with: String
+
+ """All values ending with the given string."""
+ password_ends_with: String
+
+ """All values not ending with the given string."""
+ password_not_ends_with: String
+ resetToken: String
+
+ """All values that are not equal to given value."""
+ resetToken_not: String
+
+ """All values that are contained in given list."""
+ resetToken_in: [String!]
+
+ """All values that are not contained in given list."""
+ resetToken_not_in: [String!]
+
+ """All values less than the given value."""
+ resetToken_lt: String
+
+ """All values less than or equal the given value."""
+ resetToken_lte: String
+
+ """All values greater than the given value."""
+ resetToken_gt: String
+
+ """All values greater than or equal the given value."""
+ resetToken_gte: String
+
+ """All values containing the given string."""
+ resetToken_contains: String
+
+ """All values not containing the given string."""
+ resetToken_not_contains: String
+
+ """All values starting with the given string."""
+ resetToken_starts_with: String
+
+ """All values not starting with the given string."""
+ resetToken_not_starts_with: String
+
+ """All values ending with the given string."""
+ resetToken_ends_with: String
+
+ """All values not ending with the given string."""
+ resetToken_not_ends_with: String
+ resetTokenExpiry: String
+
+ """All values that are not equal to given value."""
+ resetTokenExpiry_not: String
+
+ """All values that are contained in given list."""
+ resetTokenExpiry_in: [String!]
+
+ """All values that are not contained in given list."""
+ resetTokenExpiry_not_in: [String!]
+
+ """All values less than the given value."""
+ resetTokenExpiry_lt: String
+
+ """All values less than or equal the given value."""
+ resetTokenExpiry_lte: String
+
+ """All values greater than the given value."""
+ resetTokenExpiry_gt: String
+
+ """All values greater than or equal the given value."""
+ resetTokenExpiry_gte: String
+
+ """All values containing the given string."""
+ resetTokenExpiry_contains: String
+
+ """All values not containing the given string."""
+ resetTokenExpiry_not_contains: String
+
+ """All values starting with the given string."""
+ resetTokenExpiry_starts_with: String
+
+ """All values not starting with the given string."""
+ resetTokenExpiry_not_starts_with: String
+
+ """All values ending with the given string."""
+ resetTokenExpiry_ends_with: String
+
+ """All values not ending with the given string."""
+ resetTokenExpiry_not_ends_with: String
+ cart_every: CartItemWhereInput
+ cart_some: CartItemWhereInput
+ cart_none: CartItemWhereInput
+}
+
+input UserWhereUniqueInput {
+ id: ID
+ email: String
+}
diff --git a/stepped-solutions/46/frontend/components/CartItem.js b/stepped-solutions/46/frontend/components/CartItem.js
new file mode 100755
index 0000000..b0ce62e
--- /dev/null
+++ b/stepped-solutions/46/frontend/components/CartItem.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import styled from 'styled-components';
+import PropTypes from 'prop-types';
+import formatMoney from '../lib/formatMoney';
+import RemoveFromCart from './RemoveFromCart';
+
+const CartItemStyles = styled.li`
+ padding: 1rem 0;
+ border-bottom: 1px solid ${props => props.theme.lightgrey};
+ display: grid;
+ align-items: center;
+ grid-template-columns: auto 1fr auto;
+ img {
+ margin-right: 10px;
+ }
+ h3,
+ p {
+ margin: 0;
+ }
+`;
+
+const CartItem = ({ cartItem }) => {
+ // first check if that item exists
+ if (!cartItem.item)
+ return (
+ <CartItemStyles>
+ <p>This Item has been removed</p>
+ <RemoveFromCart id={cartItem.id} />
+ </CartItemStyles>
+ );
+ return (
+ <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.item.price * cartItem.quantity)}
+ {' - '}
+ <em>
+ {cartItem.quantity} &times; {formatMoney(cartItem.item.price)} each
+ </em>
+ </p>
+ </div>
+ <RemoveFromCart id={cartItem.id} />
+ </CartItemStyles>
+ );
+};
+
+CartItem.propTypes = {
+ cartItem: PropTypes.object.isRequired,
+};
+
+export default CartItem;