summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--backend/README.md18
-rw-r--r--backend/database/datamodel.graphql8
-rw-r--r--backend/database/prisma.yml2
-rw-r--r--backend/package.json17
-rw-r--r--backend/src/generated/prisma.graphql160
-rw-r--r--backend/src/index.js5
-rw-r--r--backend/src/mail.js12
-rw-r--r--backend/src/resolvers/Mutation.js129
-rw-r--r--backend/src/schema.graphql7
-rw-r--r--frontend/components/AddToCart.js56
-rw-r--r--frontend/components/Cart.js42
-rw-r--r--frontend/components/CartList.js2
-rw-r--r--frontend/components/Header.js2
-rw-r--r--frontend/components/Item.js2
-rw-r--r--frontend/components/RemoveFromCart.js25
-rw-r--r--frontend/components/Reset.js87
-rw-r--r--frontend/components/Signin.js2
-rw-r--r--frontend/components/Signup.js2
-rw-r--r--frontend/enhancers.js18
-rw-r--r--frontend/enhancers/enhancers.js32
-rw-r--r--frontend/lib/initApollo.js1
-rw-r--r--frontend/package.json3
-rw-r--r--frontend/pages/reset.js12
-rw-r--r--frontend/pages/signup.js2
-rw-r--r--frontend/queries/index.js41
25 files changed, 568 insertions, 119 deletions
diff --git a/backend/README.md b/backend/README.md
index 7687fd4..e1c8e10 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -2,4 +2,20 @@ What are all these files?!
What are the steps:
-1.
+
+When you want to add a new function:
+
+1. Open your `schema.graphql` and add it to the mutatiions
+2. Add a mutation resolver to match that new mutation:
+
+ ```
+ addToCart(parent, args, ctx, info) {
+ console.log('ADding an item to cart!');
+ },
+ ```
+3. Create a client-side Mutation that will accept the arguments and call it
+
+
+### Installing Docker
+
+
diff --git a/backend/database/datamodel.graphql b/backend/database/datamodel.graphql
index 40dd30c..4620206 100644
--- a/backend/database/datamodel.graphql
+++ b/backend/database/datamodel.graphql
@@ -17,6 +17,14 @@ type User {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
+ cart: [CartItem!]!
+}
+
+type CartItem {
+ id: ID! @unique
+ quantity: Int! @default(value: 1)
+ item: Item!
}
type Item {
diff --git a/backend/database/prisma.yml b/backend/database/prisma.yml
index 70e10f4..ce652ef 100644
--- a/backend/database/prisma.yml
+++ b/backend/database/prisma.yml
@@ -1,5 +1,5 @@
# the name for the service (will be part of the service's HTTP endpoint)
-service: sick-fitz
+service: sick-fits
# the cluster and stage the service is deployed to
stage: ${env:PRISMA_STAGE}
diff --git a/backend/package.json b/backend/package.json
index 86ad2b6..fda13b0 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -2,25 +2,26 @@
"name": "my-own",
"scripts": {
"start": "nodemon -e js,graphql -x node -r dotenv/config src/index.js",
- "debug":
- "nodemon -e js,graphql -x node --inspect -r dotenv/config src/index.js",
+ "debug": "nodemon -e js,graphql -x node --inspect -r dotenv/config src/index.js",
"playground": "graphql playground",
"dev": "npm-run-all --parallel start playground",
"test": "jest"
},
"dependencies": {
"bcryptjs": "2.4.3",
- "graphql-yoga": "1.2.1",
+ "graphql-yoga": "1.2.4",
"jsonwebtoken": "8.1.1",
- "prisma-binding": "1.4.0"
+ "nodemailer": "^4.4.2",
+ "prisma-binding": "1.5.7"
},
"devDependencies": {
- "dotenv": "4.0.0",
- "graphql-cli": "2.13.0",
+ "chalk": "^2.3.0",
+ "dotenv": "5.0.0",
+ "graphql-cli": "2.13.1",
"graphql-request": "^1.4.1",
"jest-cli": "^22.1.4",
- "nodemon": "1.14.11",
+ "nodemon": "1.14.12",
"npm-run-all": "4.1.2",
- "prisma": "1.0.11"
+ "prisma": "1.1.3"
}
}
diff --git a/backend/src/generated/prisma.graphql b/backend/src/generated/prisma.graphql
index 5e2d471..6132328 100644
--- a/backend/src/generated/prisma.graphql
+++ b/backend/src/generated/prisma.graphql
@@ -5,6 +5,12 @@
# Model Types
#
+type CartItem implements Node {
+ id: ID!
+ quantity: Int!
+ item(where: ItemWhereInput): Item!
+}
+
type Item implements Node {
id: ID!
title: String!
@@ -31,6 +37,8 @@ type User implements Node {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
+ cart(where: CartItemWhereInput, orderBy: CartItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [CartItem!]
}
@@ -38,6 +46,10 @@ type User implements Node {
# Other Types
#
+type AggregateCartItem {
+ count: Int!
+}
+
type AggregateItem {
count: Int!
}
@@ -54,6 +66,104 @@ type BatchPayload {
count: Long!
}
+type CartItemConnection {
+ pageInfo: PageInfo!
+ edges: [CartItemEdge]!
+ aggregate: AggregateCartItem!
+}
+
+input CartItemCreateInput {
+ quantity: Int
+ item: ItemCreateOneInput!
+}
+
+input CartItemCreateManyInput {
+ create: [CartItemCreateInput!]
+ connect: [CartItemWhereUniqueInput!]
+}
+
+type CartItemEdge {
+ node: CartItem!
+ 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 {
+ AND: [CartItemSubscriptionWhereInput!]
+ OR: [CartItemSubscriptionWhereInput!]
+ mutation_in: [MutationType!]
+ updatedFields_contains: String
+ updatedFields_contains_every: [String!]
+ updatedFields_contains_some: [String!]
+ node: CartItemWhereInput
+}
+
+input CartItemUpdateInput {
+ quantity: Int
+ item: ItemUpdateOneInput
+}
+
+input CartItemUpdateManyInput {
+ create: [CartItemCreateInput!]
+ connect: [CartItemWhereUniqueInput!]
+ disconnect: [CartItemWhereUniqueInput!]
+ delete: [CartItemWhereUniqueInput!]
+}
+
+input CartItemWhereInput {
+ AND: [CartItemWhereInput!]
+ OR: [CartItemWhereInput!]
+ id: ID
+ id_not: ID
+ id_in: [ID!]
+ id_not_in: [ID!]
+ id_lt: ID
+ id_lte: ID
+ id_gt: ID
+ id_gte: ID
+ id_contains: ID
+ id_not_contains: ID
+ id_starts_with: ID
+ id_not_starts_with: ID
+ id_ends_with: ID
+ id_not_ends_with: ID
+ quantity: Int
+ quantity_not: Int
+ quantity_in: [Int!]
+ quantity_not_in: [Int!]
+ quantity_lt: Int
+ quantity_lte: Int
+ quantity_gt: Int
+ quantity_gte: Int
+ item: ItemWhereInput
+}
+
+input CartItemWhereUniqueInput {
+ id: ID
+}
+
scalar DateTime
type ItemConnection {
@@ -68,6 +178,11 @@ input ItemCreateInput {
price: Int!
}
+input ItemCreateOneInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+}
+
type ItemEdge {
node: Item!
cursor: String!
@@ -118,6 +233,13 @@ input ItemUpdateInput {
price: Int
}
+input ItemUpdateOneInput {
+ create: ItemCreateInput
+ connect: ItemWhereUniqueInput
+ disconnect: ItemWhereUniqueInput
+ delete: ItemWhereUniqueInput
+}
+
input ItemWhereInput {
AND: [ItemWhereInput!]
OR: [ItemWhereInput!]
@@ -182,21 +304,27 @@ scalar Long
type Mutation {
createPost(data: PostCreateInput!): Post!
createUser(data: UserCreateInput!): User!
+ createCartItem(data: CartItemCreateInput!): CartItem!
createItem(data: ItemCreateInput!): Item!
updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post
updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User
+ updateCartItem(data: CartItemUpdateInput!, where: CartItemWhereUniqueInput!): CartItem
updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item
deletePost(where: PostWhereUniqueInput!): Post
deleteUser(where: UserWhereUniqueInput!): User
+ deleteCartItem(where: CartItemWhereUniqueInput!): CartItem
deleteItem(where: ItemWhereUniqueInput!): Item
upsertPost(where: PostWhereUniqueInput!, create: PostCreateInput!, update: PostUpdateInput!): Post!
upsertUser(where: UserWhereUniqueInput!, create: UserCreateInput!, update: UserUpdateInput!): User!
+ upsertCartItem(where: CartItemWhereUniqueInput!, create: CartItemCreateInput!, update: CartItemUpdateInput!): CartItem!
upsertItem(where: ItemWhereUniqueInput!, create: ItemCreateInput!, update: ItemUpdateInput!): Item!
updateManyPosts(data: PostUpdateInput!, where: PostWhereInput!): BatchPayload!
updateManyUsers(data: UserUpdateInput!, where: UserWhereInput!): BatchPayload!
+ updateManyCartItems(data: CartItemUpdateInput!, where: CartItemWhereInput!): BatchPayload!
updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput!): BatchPayload!
deleteManyPosts(where: PostWhereInput!): BatchPayload!
deleteManyUsers(where: UserWhereInput!): BatchPayload!
+ deleteManyCartItems(where: CartItemWhereInput!): BatchPayload!
deleteManyItems(where: ItemWhereInput!): BatchPayload!
}
@@ -393,12 +521,15 @@ input PostWhereUniqueInput {
type Query {
posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post]!
users(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]!
+ 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]!
post(where: PostWhereUniqueInput!): Post
user(where: UserWhereUniqueInput!): User
+ cartItem(where: CartItemWhereUniqueInput!): CartItem
item(where: ItemWhereUniqueInput!): Item
postsConnection(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): PostConnection!
usersConnection(where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): UserConnection!
+ 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!
node(id: ID!): Node
}
@@ -406,6 +537,7 @@ type Query {
type Subscription {
post(where: PostSubscriptionWhereInput): PostSubscriptionPayload
user(where: UserSubscriptionWhereInput): UserSubscriptionPayload
+ cartItem(where: CartItemSubscriptionWhereInput): CartItemSubscriptionPayload
item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload
}
@@ -422,7 +554,9 @@ input UserCreateInput {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
posts: PostCreateManyWithoutAuthorInput
+ cart: CartItemCreateManyInput
}
input UserCreateOneWithoutPostsInput {
@@ -437,6 +571,8 @@ input UserCreateWithoutPostsInput {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
+ cart: CartItemCreateManyInput
}
type UserEdge {
@@ -459,6 +595,8 @@ enum UserOrderByInput {
age_DESC
resetToken_ASC
resetToken_DESC
+ resetTokenExpiry_ASC
+ resetTokenExpiry_DESC
updatedAt_ASC
updatedAt_DESC
createdAt_ASC
@@ -473,6 +611,7 @@ type UserPreviousValues {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
}
type UserSubscriptionPayload {
@@ -499,7 +638,9 @@ input UserUpdateInput {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
posts: PostUpdateManyWithoutAuthorInput
+ cart: CartItemUpdateManyInput
}
input UserUpdateOneWithoutPostsInput {
@@ -518,6 +659,8 @@ input UserUpdateWithoutPostsDataInput {
website: String
age: Int
resetToken: String
+ resetTokenExpiry: String
+ cart: CartItemUpdateManyInput
}
input UserUpdateWithoutPostsInput {
@@ -626,9 +769,26 @@ input UserWhereInput {
resetToken_not_starts_with: String
resetToken_ends_with: String
resetToken_not_ends_with: String
+ resetTokenExpiry: String
+ resetTokenExpiry_not: String
+ resetTokenExpiry_in: [String!]
+ resetTokenExpiry_not_in: [String!]
+ resetTokenExpiry_lt: String
+ resetTokenExpiry_lte: String
+ resetTokenExpiry_gt: String
+ resetTokenExpiry_gte: String
+ resetTokenExpiry_contains: String
+ resetTokenExpiry_not_contains: String
+ resetTokenExpiry_starts_with: String
+ resetTokenExpiry_not_starts_with: String
+ resetTokenExpiry_ends_with: String
+ resetTokenExpiry_not_ends_with: String
posts_every: PostWhereInput
posts_some: PostWhereInput
posts_none: PostWhereInput
+ cart_every: CartItemWhereInput
+ cart_some: CartItemWhereInput
+ cart_none: CartItemWhereInput
}
input UserWhereUniqueInput {
diff --git a/backend/src/index.js b/backend/src/index.js
index fefe598..359456f 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -31,3 +31,8 @@ const server = new GraphQLServer({
// });
server.start(() => console.log('Server is running on http://localhost:4000'));
+
+// overwrite console.log
+const chalk = require('chalk');
+
+global.console.l = (...butta) => console.log(chalk.bold.yellow(...butta));
diff --git a/backend/src/mail.js b/backend/src/mail.js
new file mode 100644
index 0000000..3159fe2
--- /dev/null
+++ b/backend/src/mail.js
@@ -0,0 +1,12 @@
+const nodemailer = require('nodemailer');
+
+const transport = nodemailer.createTransport({
+ host: 'smtp.mailtrap.io',
+ port: 2525,
+ auth: {
+ user: 'c41bab08214808',
+ pass: '04992d4af4bdcf',
+ },
+});
+
+module.exports = transport;
diff --git a/backend/src/resolvers/Mutation.js b/backend/src/resolvers/Mutation.js
index 95be561..003a563 100644
--- a/backend/src/resolvers/Mutation.js
+++ b/backend/src/resolvers/Mutation.js
@@ -4,6 +4,7 @@ const { forwardTo } = require('prisma-binding');
const { getUserId, Context } = require('../utils');
const { randomBytes } = require('crypto');
const { promisify } = require('util');
+const mail = require('../mail');
const mutations = {
// Signup Mutations
@@ -136,16 +137,128 @@ const mutations = {
}
console.log(user);
// 2. Set a reset token, and a reset date
- // const resetToken = await promisify(randomBytes)(20);
- // const resetTokenExpiry = Date.now() + 3600000; // 1 hour from now
- // console.log({ resetToken, resetTokenExpiry });
- // const res = await ctx.db.mutation.updateUser({
- // where: { email: args.email },
- // data: { resetToken, resetTokenExpiry },
- // });
+ const resetToken = (await promisify(randomBytes)(20)).toString('hex');
+ const resetTokenExpiry = Date.now() + 3600000; // 1 hour from now
+ console.log({ resetToken, resetTokenExpiry });
+ const res = await ctx.db.mutation.updateUser({
+ where: { email: args.email },
+ data: { resetToken, resetTokenExpiry },
+ });
- // console.log(res);
+ console.log(res);
// 3. Send them their token via email
+ const mailRes = await mail.sendMail({
+ from: 'wesbos@gmail.com',
+ to: 'wesbos@gmail.com',
+ subject: 'Your password reset token',
+ html: `Here is your reset link: http://localhost:3000/reset?resetToken=${resetToken}`,
+ });
+
+ return res.updateUser;
+ },
+
+ async resetPassword(parent, args, ctx, info) {
+ // 1. Check that the passwords match
+ if (args.password !== args.confirmPassword) {
+ throw new Error('Passwords do not match');
+ }
+
+ // 2. Check that this is a legit resetToken
+ // 3. Check that it's not expired
+ // Note: If we didn't need the user here, we could also use db.exists()
+ const [user] = await ctx.db.query.users({
+ where: {
+ resetToken: args.resetToken,
+ resetTokenExpiry_gte: Date.now() - 3600000, // within the last hour
+ },
+ });
+
+ if (!user) {
+ throw new Error('This token is either invalid or expired.');
+ }
+
+ // 4. Hash the password
+ const password = await bcrypt.hash(args.password, 10);
+
+ // 5. Update the users password
+ // clean up the resetToken fields at the same time
+ const updatedUser = await ctx.db.mutation.updateUser({
+ where: { email: user.email },
+ data: {
+ password,
+ resetToken: null,
+ resetTokenExpiry: null,
+ },
+ });
+
+ // 6. send back the Auth Payload for the GraphQL request on the client
+ return {
+ // TODO: This should use sub instead of userId
+ token: jwt.sign({ userId: updatedUser.id }, process.env.APP_SECRET),
+ user: updatedUser,
+ };
+ },
+ /*
+ Add to cart
+ */
+ async addToCart(parent, args, ctx, info) {
+ console.l('Add to cart calle');
+ const userId = getUserId(ctx);
+ // get the current user
+ const currentUser = await ctx.db.query.user(
+ {
+ where: { id: userId },
+ },
+ '{ cart { id, quantity item { id } }}'
+ );
+
+ // find out if the user currently has this item in their cart
+ const existingCartItemIndex = currentUser.cart.findIndex(cartItem => cartItem.item.id === args.id);
+
+ if (existingCartItemIndex >= 0) {
+ console.l('======This item already exists in the cart============');
+ const cartItem = currentUser.cart[existingCartItemIndex];
+ return ctx.db.mutation.updateCartItem({
+ where: { id: cartItem.id },
+ data: {
+ quantity: cartItem.quantity + 1,
+ },
+ });
+ }
+
+ const res = await ctx.db.mutation.updateUser({
+ where: { id: userId },
+ data: {
+ cart: {
+ create: [
+ {
+ item: {
+ connect: {
+ id: args.id,
+ },
+ },
+ quantity: 1,
+ },
+ ],
+ },
+ },
+ });
+ console.log(res);
+ // return res;
+ },
+ async removeFromCart(parent, args, ctx, info) {
+ // delete that cart item
+ return ctx.db.mutation.deleteCartItem({
+ where: { id: args.id },
+ });
+ // const userId = getUserId(ctx);
+ // return ctx.db.query.user({ where: { id: userId } }, '{ id, cart { id, quantity }}');
+ // const cartItem = await ctx.db.query.cartItem({
+ // where: { id: args.id },
+ // });
+
+ // console.log(cartItem);
+ // return cartItem;
},
};
diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql
index 8a2fa23..b30b096 100644
--- a/backend/src/schema.graphql
+++ b/backend/src/schema.graphql
@@ -1,4 +1,4 @@
-# import ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput from './generated/prisma.graphql'
+# import CartItem, ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput from './generated/prisma.graphql'
type Query {
feed: [Post!]!
@@ -28,6 +28,7 @@ type Mutation {
signup(email: String!, password: String!, name: String!): AuthPayload!
signin(email: String!, password: String!): AuthPayload!
requestReset(email: String!): User
+ resetPassword(resetToken: String!, password: String!, confirmPassword: String!): AuthPayload!
createDraft(title: String!, text: String!): Post!
publish(id: ID!): Post!
deletePost(id: ID!): Post!
@@ -35,6 +36,8 @@ type Mutation {
createItem(title: String, description: String, price: Int): Item!
deleteItem(id: ID!): Item!
updateItem(id: ID!, title: String, description: String, price: Int): Item!
+ addToCart(id: ID!): CartItem
+ removeFromCart(id: ID!): CartItem
}
type AuthPayload {
@@ -49,5 +52,5 @@ type User {
name: String!
posts: [Post!]!
age: Int
- resetToken: String
+ cart: [CartItem!]!
}
diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js
index e46a1f5..4522137 100644
--- a/frontend/components/AddToCart.js
+++ b/frontend/components/AddToCart.js
@@ -1,11 +1,13 @@
import { Component } from 'react';
-import { ADD_TO_CART_MUTATION, CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries';
-import { removeFromCartEnhancer, userEnhancer } from '../enhancers';
import { graphql, compose } from 'react-apollo';
import Transition from 'react-transition-group/Transition';
-import makeImage from '../lib/image';
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';
+
+console.log(addtoCartEnhancer);
+import makeImage from '../lib/image';
const JumpImg = styled.img`
border: 0 solid black;
@@ -36,59 +38,29 @@ const JumpImg = styled.img`
class AddToCart extends Component {
static propTypes = {
- currentUserQuery: PropTypes.object,
+ currentUser: PropTypes.object,
};
componentDidMount() {
- this.props.currentUserQuery.refetch();
+ this.props.currentUser.refetch();
}
- addToCart = async () => {
+ handleAddToCart = async () => {
+ console.log(`Gonna add this item to the cart! ${this.props.id}`);
const res = await this.props.addToCart({
variables: {
- userId: this.props.currentUserQuery.user.id,
- itemId: this.props.id,
- },
- });
- this.props.currentUserQuery.refetch();
- console.log(res);
- };
-
- removeFromCart = async () => {
- const res = await this.props.removeFromCart({
- variables: {
- userId: this.props.currentUserQuery.user.id,
- itemId: this.props.id,
+ id: this.props.id,
},
});
- this.props.currentUserQuery.refetch();
- console.log(res);
+ console.log({ realResponse: res });
+ this.props.currentUser.refetch();
};
render() {
- const user = this.props.currentUserQuery.user;
- // TODO WTF
- if (!user || this.props.singleItemQuery.loading || !this.props.singleItemQuery.Item) return <p>Loading...</p>;
- const cartIds = user.cart.map(item => item.id);
- const image = this.props.singleItemQuery.Item.image || {};
- const isInCart = cartIds.includes(this.props.id);
- const { x, y } = document.querySelector('.cart').getBoundingClientRect();
- return (
- <div>
- {isInCart ? (
- <button onClick={this.removeFromCart}>❌ Remove From Cart</button>
- ) : (
- <button onClick={this.addToCart}>Add To Cart 👜</button>
- )}
- <Transition in={isInCart} timeout={1000}>
- {status => <JumpImg x={x} y={y} src={makeImage(image)} className={`jump-${status}`} />}
- </Transition>
- </div>
- );
+ return <button onClick={this.handleAddToCart}>🛒 Add To Cart</button>;
}
}
-const createOrderEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart' });
const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, {
name: 'singleItemQuery',
options: ({ id }) => ({
@@ -97,4 +69,4 @@ const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, {
},
}),
});
-export default compose(userEnhancer, createOrderEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart);
+export default compose(userEnhancer, addtoCartEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart);
diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js
index 88b072c..4153600 100644
--- a/frontend/components/Cart.js
+++ b/frontend/components/Cart.js
@@ -2,8 +2,7 @@ import { Component } from 'react';
import { graphql, compose } from 'react-apollo';
import styled from 'styled-components';
import { CURRENT_USER_QUERY } from '../queries';
-import formatMoney from '../lib/formatMoney.js';
-import ChaChing from './ChaChing';
+import RemoveFromCart from './RemoveFromCart';
const CartStyles = styled.div`
background: white;
@@ -16,31 +15,44 @@ const CartStyles = styled.div`
class Cart extends Component {
componentDidMount() {
- // This fetches the new data, but doesn't populate the user via props
- // this.props.currentUserQuery.refetch();
- // This fetches the new data, and populates the user via props
- setTimeout(this.props.currentUserQuery.refetch, 1);
+ console.log('refetching..');
+ setTimeout(this.props.currentUser.refetch, 10);
}
-
render() {
+ if (!this.props.currentUser.me) {
+ return null;
+ }
// Check for loading state..
- const { loading, error } = this.props.currentUserQuery;
- const { user } = this.props.currentUserQuery;
- if (loading || error || !user) return <p>Cart Loading...</p>;
- const total = user.cart.reduce((a, b) => a + b.price, 0);
+ // const { loading, error } = this.props.currentUserQuery;
+ // const { user } = this.props.currentUserQuery;
+ // if (loading || error || !user) return <p>Cart Loading...</p>;
+ // const total = user.cart.reduce((a, b) => a + b.price, 0);
+ const { me } = this.props.currentUser;
+ console.log(me);
return (
<CartStyles className="cart">
- There
+ <p>🛒: {me.cart.length} Items</p>
+ <ul>
+ {me.cart.map(cartItem => (
+ <li key={cartItem.id}>
+ <strong>
+ {cartItem.quantity} of {cartItem.item.title}
+ </strong>
+ <RemoveFromCart id={cartItem.id} />
+ </li>
+ ))}
+ </ul>
+ {/* There
{user.cart.length === 1 ? ' is ' : 'are '}
<ChaChing amount={user.cart.length} />
{user.cart.length === 1 ? ' item ' : ' items '}
- in your cart totaling
- <ChaChing amount={formatMoney(total)} />
+ in your cart totaling */}
+ {/* <ChaChing amount={formatMoney(total)} /> */}
</CartStyles>
);
}
}
-const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' });
+const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
export default compose(userEnhancer)(Cart);
diff --git a/frontend/components/CartList.js b/frontend/components/CartList.js
index b54aa39..661d5ad 100644
--- a/frontend/components/CartList.js
+++ b/frontend/components/CartList.js
@@ -12,7 +12,7 @@ import styled from 'styled-components';
import formatMoney from '../lib/formatMoney';
import makeImage from '../lib/image';
import TakeMyMoney from './TakeMyMoney';
-import { removeFromCartEnhancer } from '../enhancers';
+import { removeFromCartEnhancer } from '../enhancers/enhancers';
import { CURRENT_USER_QUERY } from '../queries';
import PropTypes from 'prop-types';
diff --git a/frontend/components/Header.js b/frontend/components/Header.js
index 5e1a620..774bd0b 100644
--- a/frontend/components/Header.js
+++ b/frontend/components/Header.js
@@ -28,7 +28,7 @@ class Header extends Component {
<div>
{this.props.currentUser.me ? this.props.currentUser.me.email : 'Not Signed in'}
<Signout />
- {/* <Cart /> */}
+ <Cart />
{/* <Search /> */}
</div>
);
diff --git a/frontend/components/Item.js b/frontend/components/Item.js
index d19af0e..2a0fc69 100644
--- a/frontend/components/Item.js
+++ b/frontend/components/Item.js
@@ -59,7 +59,7 @@ class ItemComponent extends React.Component {
<button>Buy for {formatMoney(item.price)}</button>
</TakeMyMoney>
- {/* <AddToCart id={item.id} /> */}
+ <AddToCart id={item.id} />
<button onClick={this.removeItem}>&times; Delete item</button>
</Item>
);
diff --git a/frontend/components/RemoveFromCart.js b/frontend/components/RemoveFromCart.js
new file mode 100644
index 0000000..e3eb919
--- /dev/null
+++ b/frontend/components/RemoveFromCart.js
@@ -0,0 +1,25 @@
+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 { removeFromCartEnhancer } from '../enhancers/enhancers';
+
+class RemoveFromCart extends Component {
+ handleRemoveFromCart = async () => {
+ console.log(`gonna remove item from cart`);
+ const res = await this.props.removeFromCart({
+ variables: {
+ id: this.props.id,
+ },
+ });
+ console.log('This came back from removeFromCart:', res);
+ // this.props.currentUserQuery.refetch();
+ };
+
+ render() {
+ return <button onClick={this.handleRemoveFromCart}>&times; Remove</button>;
+ }
+}
+
+export default compose(removeFromCartEnhancer)(RemoveFromCart);
diff --git a/frontend/components/Reset.js b/frontend/components/Reset.js
new file mode 100644
index 0000000..7d36de7
--- /dev/null
+++ b/frontend/components/Reset.js
@@ -0,0 +1,87 @@
+import React, { Component } from 'react';
+import { graphql, compose } from 'react-apollo';
+import { RESET_MUTATION, CURRENT_USER_QUERY } from '../queries';
+
+class Reset extends Component {
+ state = {
+ confirmPassword: '',
+ password: '',
+ errors: [],
+ };
+
+ saveToState = e => {
+ const { name, value } = e.target;
+ this.setState({ [name]: value });
+ };
+
+ resetPassword = async e => {
+ console.log(e);
+ 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!');
+ console.log(res);
+ // sign them in!
+ localStorage.setItem('token', res.data.resetPassword.token);
+ // refresh the current user query
+ this.props.currentUser.refetch();
+ };
+
+ 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>
+
+ <button type="submit">Request Reset!</button>
+ </form>
+ </div>
+ );
+ }
+}
+
+// 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);
diff --git a/frontend/components/Signin.js b/frontend/components/Signin.js
index aac1222..752199b 100644
--- a/frontend/components/Signin.js
+++ b/frontend/components/Signin.js
@@ -37,7 +37,7 @@ class Signin extends Component {
render() {
return (
<div>
- {this.state.loading ? 'LOADING...' : 'Ready!'}
+ {this.state.loading ? 'LOADING...' : null}
{this.state.errors ? this.state.errors.map(err => <p>{err.message}</p>) : null}
diff --git a/frontend/components/Signup.js b/frontend/components/Signup.js
index 746f1e4..e9d6737 100644
--- a/frontend/components/Signup.js
+++ b/frontend/components/Signup.js
@@ -42,7 +42,7 @@ class Signup extends Component {
render() {
return (
<div>
- {this.state.loading ? 'LOADING...' : 'Ready!'}
+ {this.state.loading ? 'LOADING...' : null}
{this.state.error ? <p>{this.state.error.message}</p> : null}
diff --git a/frontend/enhancers.js b/frontend/enhancers.js
deleted file mode 100644
index d86ca6d..0000000
--- a/frontend/enhancers.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { REMOVE_FROM_CART_MUTATION, CURRENT_USER_QUERY } from './queries';
-
-import { graphql } from 'react-apollo';
-
-export const removeFromCartEnhancer = graphql(REMOVE_FROM_CART_MUTATION, {
- name: 'removeFromCart',
- options: {
- update: (proxy, payload) => {
- const data = proxy.readQuery({ query: CURRENT_USER_QUERY });
- const cartItemId = payload.data.removeFromCartItems.cartItem.id;
- data.user.cart = data.user.cart.filter(item => item.id !== cartItemId);
- proxy.writeQuery({ query: CURRENT_USER_QUERY, data });
- },
- },
-});
-
-// Current User Query
-export const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' });
diff --git a/frontend/enhancers/enhancers.js b/frontend/enhancers/enhancers.js
index ff1712f..f7021c3 100644
--- a/frontend/enhancers/enhancers.js
+++ b/frontend/enhancers/enhancers.js
@@ -1,5 +1,12 @@
-import { ALL_ITEMS_QUERY, REMOVE_ITEM_MUTATION, SINGLE_ITEM_QUERY } from '../queries/index';
import { graphql } from 'react-apollo';
+import {
+ REMOVE_FROM_CART_MUTATION,
+ ALL_ITEMS_QUERY,
+ REMOVE_ITEM_MUTATION,
+ SINGLE_ITEM_QUERY,
+ CURRENT_USER_QUERY,
+ ADD_TO_CART_MUTATION,
+} from '../queries/index';
export const itemEnhancer = graphql(ALL_ITEMS_QUERY, {
name: 'itemsQuery',
@@ -35,3 +42,26 @@ export const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, {
variables: { id },
}),
});
+
+export const removeFromCartEnhancer = graphql(REMOVE_FROM_CART_MUTATION, {
+ name: 'removeFromCart',
+ options: {
+ 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 });
+ },
+ },
+});
+
+export const addtoCartEnhancer = graphql(ADD_TO_CART_MUTATION, {
+ name: 'addToCart',
+ options: {
+ update: (proxy, payload) => {
+ console.log('=----asdf-asd-fas-df-asdf-sa-df');
+ },
+ },
+});
+
+export const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' });
diff --git a/frontend/lib/initApollo.js b/frontend/lib/initApollo.js
index 7252a00..d0f1351 100644
--- a/frontend/lib/initApollo.js
+++ b/frontend/lib/initApollo.js
@@ -33,7 +33,6 @@ function create(initialState) {
if (typeof localStorage !== 'undefined' && localStorage.getItem('token')) {
headers.authorization = `Bearer ${localStorage.getItem('token')}`;
}
- console.log({ headers });
operation.setContext({ headers });
return forward(operation);
diff --git a/frontend/package.json b/frontend/package.json
index 53e5f51..547a576 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -32,7 +32,6 @@
"micro-dev": "^2.2.0",
"next": "^4.2.3",
"next-routes": "^1.2.0",
- "nodemailer": "^4.4.2",
"nprogress": "^0.2.0",
"prop-types": "^15.6.0",
"react": "^16.2.0",
@@ -48,4 +47,4 @@
"devDependencies": {
"jest": "^22.1.4"
}
-}
+} \ No newline at end of file
diff --git a/frontend/pages/reset.js b/frontend/pages/reset.js
new file mode 100644
index 0000000..899ebbc
--- /dev/null
+++ b/frontend/pages/reset.js
@@ -0,0 +1,12 @@
+import withData from '../lib/withData';
+import Page from '../components/Page';
+import Reset from '../components/Reset';
+
+const ResetPage = props => (
+ <Page>
+ <h2>So you wanna reset your password?</h2>
+ <Reset resetToken={props.url.query.resetToken} />
+ </Page>
+);
+
+export default withData(ResetPage);
diff --git a/frontend/pages/signup.js b/frontend/pages/signup.js
index 1ea3794..d3b98d7 100644
--- a/frontend/pages/signup.js
+++ b/frontend/pages/signup.js
@@ -9,7 +9,7 @@ const SignUpPage = () => (
<Page>
<p>I'm the Sign Up url!</p>
<Signup />
- {/* <Signin /> */}
+ <Signin />
<RequestReset />
</Page>
);
diff --git a/frontend/queries/index.js b/frontend/queries/index.js
index ea02c8b..e61a9d5 100644
--- a/frontend/queries/index.js
+++ b/frontend/queries/index.js
@@ -44,6 +44,19 @@ export const REQUEST_RESET_MUTATION = gql`
}
`;
+export const RESET_MUTATION = gql`
+ mutation resetPassword($resetToken: String!, $password: String!, $confirmPassword: String!) {
+ resetPassword(resetToken: $resetToken, password: $password, confirmPassword: $confirmPassword) {
+ token
+ user {
+ id
+ email
+ name
+ }
+ }
+ }
+`;
+
export const CREATE_ORDER_MUTATION = gql`
mutation CreateOrderMutation($token: String!, $userId: ID!, $itemId: ID!) {
createOrder(token: $token, userId: $userId, itemId: $itemId) {
@@ -142,11 +155,19 @@ export const UPDATE_LINK_MUTATION = gql`
`;
export const CURRENT_USER_QUERY = gql`
+ ${itemDetails}
query userQuery {
me {
id
email
name
+ cart {
+ id
+ quantity
+ item {
+ ...itemDetails
+ }
+ }
}
}
`;
@@ -176,25 +197,17 @@ export const USER_ORDERS_QUERY = gql`
`;
export const ADD_TO_CART_MUTATION = gql`
- mutation AddToCart($userId: ID!, $itemId: ID!) {
- addToCartItems(userUserId: $userId, cartItemId: $itemId) {
- cartItem {
- id
- title
- price
- }
+ mutation addToCart($id: ID!) {
+ addToCart(id: $id) {
+ id
}
}
`;
export const REMOVE_FROM_CART_MUTATION = gql`
- mutation xxx($userId: ID!, $itemId: ID!) {
- removeFromCartItems(userUserId: $userId, cartItemId: $itemId) {
- cartItem {
- id
- title
- price
- }
+ mutation removeFromCart($id: ID!) {
+ removeFromCart(id: $id) {
+ id
}
}
`;