diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-02-13 13:19:23 -0500 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-02-13 13:19:23 -0500 |
| commit | fa3ec3299b3fa33e5a699683beb96cfa4e6dd51d (patch) | |
| tree | 601b4ff9650cd17a6d4181fc5982b401411d2a6a | |
| parent | e0d0baa75ec0bf617f91b6cd779305d4009cae12 (diff) | |
getting there
| -rw-r--r-- | backend/README.md | 11 | ||||
| -rw-r--r-- | backend/database/datamodel.graphql | 13 | ||||
| -rw-r--r-- | backend/database/prisma.yml | 3 | ||||
| -rw-r--r-- | backend/package.json | 12 | ||||
| -rw-r--r-- | backend/src/generated/prisma.graphql | 350 | ||||
| -rw-r--r-- | backend/src/index.js | 2 | ||||
| -rw-r--r-- | backend/src/resolvers/Mutation.js | 121 | ||||
| -rw-r--r-- | backend/src/resolvers/Query.js | 7 | ||||
| -rw-r--r-- | backend/src/schema.graphql | 6 | ||||
| -rw-r--r-- | backend/src/stripe.js | 1 | ||||
| -rw-r--r-- | frontend/components/AddToCart.js | 5 | ||||
| -rw-r--r-- | frontend/components/Cart.js | 7 | ||||
| -rw-r--r-- | frontend/components/CreateItem.js | 17 | ||||
| -rw-r--r-- | frontend/components/Header.js | 4 | ||||
| -rw-r--r-- | frontend/components/Item.js | 14 | ||||
| -rw-r--r-- | frontend/components/Page.js | 2 | ||||
| -rw-r--r-- | frontend/components/SingleItem.js | 3 | ||||
| -rw-r--r-- | frontend/components/TakeMyMoney.js | 46 | ||||
| -rw-r--r-- | frontend/enhancers/enhancers.js | 11 | ||||
| -rw-r--r-- | frontend/queries/fragments.js | 1 | ||||
| -rw-r--r-- | frontend/queries/index.js | 33 |
21 files changed, 560 insertions, 109 deletions
diff --git a/backend/README.md b/backend/README.md index e1c8e10..765d19e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -18,4 +18,15 @@ When you want to add a new function: ### Installing Docker +1. download docker +1. run `prisma local start` +1. Run `prisma deploy` +if in trouble, run `prisma local nuke` + + + +### Cloudinary + +1. go to your settings and turn on unsigned uploading +2.
\ No newline at end of file diff --git a/backend/database/datamodel.graphql b/backend/database/datamodel.graphql index 4620206..ed70892 100644 --- a/backend/database/datamodel.graphql +++ b/backend/database/datamodel.graphql @@ -14,6 +14,7 @@ type User { password: String! name: String! posts: [Post!]! + orders: [Order!]! website: String age: Int resetToken: String @@ -24,12 +25,22 @@ type User { type CartItem { id: ID! @unique quantity: Int! @default(value: 1) - item: Item! + item: Item! # Relationship to an Item + user: User! # Relationship to a user } type Item { id: ID! @unique title: String! description: String! + image: String price: Int! } + +type Order { + id: ID! @unique + items: [Item!]! + total: Int! + user: User! + charge: String! +}
\ No newline at end of file diff --git a/backend/database/prisma.yml b/backend/database/prisma.yml index ce652ef..b01378b 100644 --- a/backend/database/prisma.yml +++ b/backend/database/prisma.yml @@ -15,5 +15,6 @@ datamodel: datamodel.graphql # seed: # import: seed.graphql -cluster: ${env:PRISMA_CLUSTER} +# cluster: ${env:PRISMA_CLUSTER} +cluster: local diff --git a/backend/package.json b/backend/package.json index fda13b0..10b57aa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,19 +9,25 @@ }, "dependencies": { "bcryptjs": "2.4.3", + "graphql": "^0.12.3", "graphql-yoga": "1.2.4", "jsonwebtoken": "8.1.1", "nodemailer": "^4.4.2", - "prisma-binding": "1.5.7" + "prisma-binding": "1.5.7", + "stripe": "^5.4.0" }, "devDependencies": { "chalk": "^2.3.0", "dotenv": "5.0.0", - "graphql-cli": "2.13.1", + "graphql-cli": "2.14.0", "graphql-request": "^1.4.1", "jest-cli": "^22.1.4", "nodemon": "1.14.12", "npm-run-all": "4.1.2", - "prisma": "1.1.3" + "prisma": "1.2.0" + }, + "license": "MIT", + "repository": { + "url": "wesbos.com" } } diff --git a/backend/src/generated/prisma.graphql b/backend/src/generated/prisma.graphql index 6132328..402e097 100644 --- a/backend/src/generated/prisma.graphql +++ b/backend/src/generated/prisma.graphql @@ -9,15 +9,25 @@ type CartItem implements Node { id: ID! quantity: Int! item(where: ItemWhereInput): Item! + user(where: UserWhereInput): User! } type Item implements Node { id: ID! title: String! description: String! + image: String price: Int! } +type Order implements Node { + id: ID! + items(where: ItemWhereInput, orderBy: ItemOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Item!] + total: Int! + user(where: UserWhereInput): User! + charge: String! +} + type Post implements Node { id: ID! createdAt: DateTime! @@ -34,6 +44,7 @@ type User implements Node { password: String! name: String! posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post!] + orders(where: OrderWhereInput, orderBy: OrderOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Order!] website: String age: Int resetToken: String @@ -54,6 +65,10 @@ type AggregateItem { count: Int! } +type AggregateOrder { + count: Int! +} + type AggregatePost { count: Int! } @@ -75,13 +90,19 @@ type CartItemConnection { input CartItemCreateInput { quantity: Int item: ItemCreateOneInput! + user: UserCreateOneWithoutCartInput! } -input CartItemCreateManyInput { - create: [CartItemCreateInput!] +input CartItemCreateManyWithoutUserInput { + create: [CartItemCreateWithoutUserInput!] connect: [CartItemWhereUniqueInput!] } +input CartItemCreateWithoutUserInput { + quantity: Int + item: ItemCreateOneInput! +} + type CartItemEdge { node: CartItem! cursor: String! @@ -123,13 +144,32 @@ input CartItemSubscriptionWhereInput { input CartItemUpdateInput { quantity: Int item: ItemUpdateOneInput + user: UserUpdateOneWithoutCartInput } -input CartItemUpdateManyInput { - create: [CartItemCreateInput!] +input CartItemUpdateManyWithoutUserInput { + create: [CartItemCreateWithoutUserInput!] connect: [CartItemWhereUniqueInput!] disconnect: [CartItemWhereUniqueInput!] delete: [CartItemWhereUniqueInput!] + update: [CartItemUpdateWithoutUserInput!] + upsert: [CartItemUpsertWithoutUserInput!] +} + +input CartItemUpdateWithoutUserDataInput { + quantity: Int + item: ItemUpdateOneInput +} + +input CartItemUpdateWithoutUserInput { + where: CartItemWhereUniqueInput! + data: CartItemUpdateWithoutUserDataInput! +} + +input CartItemUpsertWithoutUserInput { + where: CartItemWhereUniqueInput! + update: CartItemUpdateWithoutUserDataInput! + create: CartItemCreateWithoutUserInput! } input CartItemWhereInput { @@ -158,6 +198,7 @@ input CartItemWhereInput { quantity_gt: Int quantity_gte: Int item: ItemWhereInput + user: UserWhereInput } input CartItemWhereUniqueInput { @@ -175,9 +216,15 @@ type ItemConnection { input ItemCreateInput { title: String! description: String! + image: String price: Int! } +input ItemCreateManyInput { + create: [ItemCreateInput!] + connect: [ItemWhereUniqueInput!] +} + input ItemCreateOneInput { create: ItemCreateInput connect: ItemWhereUniqueInput @@ -195,6 +242,8 @@ enum ItemOrderByInput { title_DESC description_ASC description_DESC + image_ASC + image_DESC price_ASC price_DESC updatedAt_ASC @@ -207,6 +256,7 @@ type ItemPreviousValues { id: ID! title: String! description: String! + image: String price: Int! } @@ -230,9 +280,17 @@ input ItemSubscriptionWhereInput { input ItemUpdateInput { title: String description: String + image: String price: Int } +input ItemUpdateManyInput { + create: [ItemCreateInput!] + connect: [ItemWhereUniqueInput!] + disconnect: [ItemWhereUniqueInput!] + delete: [ItemWhereUniqueInput!] +} + input ItemUpdateOneInput { create: ItemCreateInput connect: ItemWhereUniqueInput @@ -285,6 +343,20 @@ input ItemWhereInput { description_not_starts_with: String description_ends_with: String description_not_ends_with: String + image: String + image_not: String + image_in: [String!] + image_not_in: [String!] + image_lt: String + image_lte: String + image_gt: String + image_gte: String + image_contains: String + image_not_contains: String + image_starts_with: String + image_not_starts_with: String + image_ends_with: String + image_not_ends_with: String price: Int price_not: Int price_in: [Int!] @@ -306,26 +378,32 @@ type Mutation { createUser(data: UserCreateInput!): User! createCartItem(data: CartItemCreateInput!): CartItem! createItem(data: ItemCreateInput!): Item! + createOrder(data: OrderCreateInput!): Order! updatePost(data: PostUpdateInput!, where: PostWhereUniqueInput!): Post updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User updateCartItem(data: CartItemUpdateInput!, where: CartItemWhereUniqueInput!): CartItem updateItem(data: ItemUpdateInput!, where: ItemWhereUniqueInput!): Item + updateOrder(data: OrderUpdateInput!, where: OrderWhereUniqueInput!): Order deletePost(where: PostWhereUniqueInput!): Post deleteUser(where: UserWhereUniqueInput!): User deleteCartItem(where: CartItemWhereUniqueInput!): CartItem deleteItem(where: ItemWhereUniqueInput!): Item + deleteOrder(where: OrderWhereUniqueInput!): Order 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! + upsertOrder(where: OrderWhereUniqueInput!, create: OrderCreateInput!, update: OrderUpdateInput!): Order! updateManyPosts(data: PostUpdateInput!, where: PostWhereInput!): BatchPayload! updateManyUsers(data: UserUpdateInput!, where: UserWhereInput!): BatchPayload! updateManyCartItems(data: CartItemUpdateInput!, where: CartItemWhereInput!): BatchPayload! updateManyItems(data: ItemUpdateInput!, where: ItemWhereInput!): BatchPayload! + updateManyOrders(data: OrderUpdateInput!, where: OrderWhereInput!): BatchPayload! deleteManyPosts(where: PostWhereInput!): BatchPayload! deleteManyUsers(where: UserWhereInput!): BatchPayload! deleteManyCartItems(where: CartItemWhereInput!): BatchPayload! deleteManyItems(where: ItemWhereInput!): BatchPayload! + deleteManyOrders(where: OrderWhereInput!): BatchPayload! } enum MutationType { @@ -338,6 +416,153 @@ interface Node { id: ID! } +type OrderConnection { + pageInfo: PageInfo! + edges: [OrderEdge]! + aggregate: AggregateOrder! +} + +input OrderCreateInput { + total: Int! + charge: String! + items: ItemCreateManyInput + user: UserCreateOneWithoutOrdersInput! +} + +input OrderCreateManyWithoutUserInput { + create: [OrderCreateWithoutUserInput!] + connect: [OrderWhereUniqueInput!] +} + +input OrderCreateWithoutUserInput { + total: Int! + charge: String! + items: ItemCreateManyInput +} + +type OrderEdge { + node: Order! + cursor: String! +} + +enum OrderOrderByInput { + id_ASC + id_DESC + total_ASC + total_DESC + charge_ASC + charge_DESC + updatedAt_ASC + updatedAt_DESC + createdAt_ASC + createdAt_DESC +} + +type OrderPreviousValues { + id: ID! + total: Int! + charge: String! +} + +type OrderSubscriptionPayload { + mutation: MutationType! + node: Order + updatedFields: [String!] + previousValues: OrderPreviousValues +} + +input OrderSubscriptionWhereInput { + AND: [OrderSubscriptionWhereInput!] + OR: [OrderSubscriptionWhereInput!] + mutation_in: [MutationType!] + updatedFields_contains: String + updatedFields_contains_every: [String!] + updatedFields_contains_some: [String!] + node: OrderWhereInput +} + +input OrderUpdateInput { + total: Int + charge: String + items: ItemUpdateManyInput + user: UserUpdateOneWithoutOrdersInput +} + +input OrderUpdateManyWithoutUserInput { + create: [OrderCreateWithoutUserInput!] + connect: [OrderWhereUniqueInput!] + disconnect: [OrderWhereUniqueInput!] + delete: [OrderWhereUniqueInput!] + update: [OrderUpdateWithoutUserInput!] + upsert: [OrderUpsertWithoutUserInput!] +} + +input OrderUpdateWithoutUserDataInput { + total: Int + charge: String + items: ItemUpdateManyInput +} + +input OrderUpdateWithoutUserInput { + where: OrderWhereUniqueInput! + data: OrderUpdateWithoutUserDataInput! +} + +input OrderUpsertWithoutUserInput { + where: OrderWhereUniqueInput! + update: OrderUpdateWithoutUserDataInput! + create: OrderCreateWithoutUserInput! +} + +input OrderWhereInput { + AND: [OrderWhereInput!] + OR: [OrderWhereInput!] + 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 + total: Int + total_not: Int + total_in: [Int!] + total_not_in: [Int!] + total_lt: Int + total_lte: Int + total_gt: Int + total_gte: Int + charge: String + charge_not: String + charge_in: [String!] + charge_not_in: [String!] + charge_lt: String + charge_lte: String + charge_gt: String + charge_gte: String + charge_contains: String + charge_not_contains: String + charge_starts_with: String + charge_not_starts_with: String + charge_ends_with: String + charge_not_ends_with: String + items_every: ItemWhereInput + items_some: ItemWhereInput + items_none: ItemWhereInput + user: UserWhereInput +} + +input OrderWhereUniqueInput { + id: ID +} + type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! @@ -523,14 +748,17 @@ type Query { 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]! + orders(where: OrderWhereInput, orderBy: OrderOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Order]! post(where: PostWhereUniqueInput!): Post user(where: UserWhereUniqueInput!): User cartItem(where: CartItemWhereUniqueInput!): CartItem item(where: ItemWhereUniqueInput!): Item + order(where: OrderWhereUniqueInput!): Order 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! + ordersConnection(where: OrderWhereInput, orderBy: OrderOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): OrderConnection! node(id: ID!): Node } @@ -539,6 +767,7 @@ type Subscription { user(where: UserSubscriptionWhereInput): UserSubscriptionPayload cartItem(where: CartItemSubscriptionWhereInput): CartItemSubscriptionPayload item(where: ItemSubscriptionWhereInput): ItemSubscriptionPayload + order(where: OrderSubscriptionWhereInput): OrderSubscriptionPayload } type UserConnection { @@ -556,7 +785,18 @@ input UserCreateInput { resetToken: String resetTokenExpiry: String posts: PostCreateManyWithoutAuthorInput - cart: CartItemCreateManyInput + orders: OrderCreateManyWithoutUserInput + cart: CartItemCreateManyWithoutUserInput +} + +input UserCreateOneWithoutCartInput { + create: UserCreateWithoutCartInput + connect: UserWhereUniqueInput +} + +input UserCreateOneWithoutOrdersInput { + create: UserCreateWithoutOrdersInput + connect: UserWhereUniqueInput } input UserCreateOneWithoutPostsInput { @@ -564,6 +804,30 @@ input UserCreateOneWithoutPostsInput { connect: UserWhereUniqueInput } +input UserCreateWithoutCartInput { + email: String! + password: String! + name: String! + website: String + age: Int + resetToken: String + resetTokenExpiry: String + posts: PostCreateManyWithoutAuthorInput + orders: OrderCreateManyWithoutUserInput +} + +input UserCreateWithoutOrdersInput { + email: String! + password: String! + name: String! + website: String + age: Int + resetToken: String + resetTokenExpiry: String + posts: PostCreateManyWithoutAuthorInput + cart: CartItemCreateManyWithoutUserInput +} + input UserCreateWithoutPostsInput { email: String! password: String! @@ -572,7 +836,8 @@ input UserCreateWithoutPostsInput { age: Int resetToken: String resetTokenExpiry: String - cart: CartItemCreateManyInput + orders: OrderCreateManyWithoutUserInput + cart: CartItemCreateManyWithoutUserInput } type UserEdge { @@ -640,7 +905,26 @@ input UserUpdateInput { resetToken: String resetTokenExpiry: String posts: PostUpdateManyWithoutAuthorInput - cart: CartItemUpdateManyInput + orders: OrderUpdateManyWithoutUserInput + cart: CartItemUpdateManyWithoutUserInput +} + +input UserUpdateOneWithoutCartInput { + create: UserCreateWithoutCartInput + connect: UserWhereUniqueInput + disconnect: UserWhereUniqueInput + delete: UserWhereUniqueInput + update: UserUpdateWithoutCartInput + upsert: UserUpsertWithoutCartInput +} + +input UserUpdateOneWithoutOrdersInput { + create: UserCreateWithoutOrdersInput + connect: UserWhereUniqueInput + disconnect: UserWhereUniqueInput + delete: UserWhereUniqueInput + update: UserUpdateWithoutOrdersInput + upsert: UserUpsertWithoutOrdersInput } input UserUpdateOneWithoutPostsInput { @@ -652,6 +936,40 @@ input UserUpdateOneWithoutPostsInput { upsert: UserUpsertWithoutPostsInput } +input UserUpdateWithoutCartDataInput { + email: String + password: String + name: String + website: String + age: Int + resetToken: String + resetTokenExpiry: String + posts: PostUpdateManyWithoutAuthorInput + orders: OrderUpdateManyWithoutUserInput +} + +input UserUpdateWithoutCartInput { + where: UserWhereUniqueInput! + data: UserUpdateWithoutCartDataInput! +} + +input UserUpdateWithoutOrdersDataInput { + email: String + password: String + name: String + website: String + age: Int + resetToken: String + resetTokenExpiry: String + posts: PostUpdateManyWithoutAuthorInput + cart: CartItemUpdateManyWithoutUserInput +} + +input UserUpdateWithoutOrdersInput { + where: UserWhereUniqueInput! + data: UserUpdateWithoutOrdersDataInput! +} + input UserUpdateWithoutPostsDataInput { email: String password: String @@ -660,7 +978,8 @@ input UserUpdateWithoutPostsDataInput { age: Int resetToken: String resetTokenExpiry: String - cart: CartItemUpdateManyInput + orders: OrderUpdateManyWithoutUserInput + cart: CartItemUpdateManyWithoutUserInput } input UserUpdateWithoutPostsInput { @@ -668,6 +987,18 @@ input UserUpdateWithoutPostsInput { data: UserUpdateWithoutPostsDataInput! } +input UserUpsertWithoutCartInput { + where: UserWhereUniqueInput! + update: UserUpdateWithoutCartDataInput! + create: UserCreateWithoutCartInput! +} + +input UserUpsertWithoutOrdersInput { + where: UserWhereUniqueInput! + update: UserUpdateWithoutOrdersDataInput! + create: UserCreateWithoutOrdersInput! +} + input UserUpsertWithoutPostsInput { where: UserWhereUniqueInput! update: UserUpdateWithoutPostsDataInput! @@ -786,6 +1117,9 @@ input UserWhereInput { posts_every: PostWhereInput posts_some: PostWhereInput posts_none: PostWhereInput + orders_every: OrderWhereInput + orders_some: OrderWhereInput + orders_none: OrderWhereInput cart_every: CartItemWhereInput cart_some: CartItemWhereInput cart_none: CartItemWhereInput diff --git a/backend/src/index.js b/backend/src/index.js index 359456f..22cc9ef 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -19,7 +19,7 @@ const server = new GraphQLServer({ typeDefs: 'src/generated/prisma.graphql', endpoint: process.env.PRISMA_ENDPOINT, // the endpoint of the Prisma DB service (value is set in .env) secret: process.env.PRISMA_SECRET, // taken from database/prisma.yml (value is set in .env) - debug: true, // log all GraphQL queries & mutations + debug: false, // log all GraphQL queries & mutations }), }), }); diff --git a/backend/src/resolvers/Mutation.js b/backend/src/resolvers/Mutation.js index 003a563..1c071fe 100644 --- a/backend/src/resolvers/Mutation.js +++ b/backend/src/resolvers/Mutation.js @@ -5,6 +5,7 @@ const { getUserId, Context } = require('../utils'); const { randomBytes } = require('crypto'); const { promisify } = require('util'); const mail = require('../mail'); +const stripe = require('../stripe'); const mutations = { // Signup Mutations @@ -39,6 +40,8 @@ const mutations = { // Creation of Post Mutations async createItem(parent, args, ctx, info) { + // TODO - they should be signed in when creating an item for sale + // TODO: The user should be saved to the item so they can manage it return ctx.db.mutation.createItem({ data: { ...args } }, info); }, @@ -202,52 +205,49 @@ const mutations = { Add to cart */ async addToCart(parent, args, ctx, info) { - console.l('Add to cart calle'); + console.l('Add to cart called'); const userId = getUserId(ctx); - // get the current user - const currentUser = await ctx.db.query.user( - { - where: { id: userId }, - }, - '{ cart { id, quantity item { id } }}' - ); + if (!userId) { + throw new Error('You must be signed in to add to cart!'); + } - // find out if the user currently has this item in their cart - const existingCartItemIndex = currentUser.cart.findIndex(cartItem => cartItem.item.id === args.id); + // 1. Check if there is a CartItem for this user and item already + const [existingCartItem] = await ctx.db.query.cartItems({ + where: { + user: { id: userId }, + 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, + if (existingCartItem) { + return ctx.db.mutation.updateCartItem( + { + where: { id: existingCartItem.id }, + data: { quantity: existingCartItem.quantity + 1 }, }, - }); + info + ); } - const res = await ctx.db.mutation.updateUser({ - where: { id: userId }, - data: { - cart: { - create: [ - { - item: { - connect: { - id: args.id, - }, - }, - quantity: 1, + // Otherwise create a new cartItem + return ctx.db.mutation.createCartItem( + { + data: { + user: { + connect: { + id: userId, }, - ], + }, + item: { + connect: { id: args.id }, + }, }, }, - }); - console.log(res); - // return res; + info + ); }, + // delete that cart item async removeFromCart(parent, args, ctx, info) { - // delete that cart item return ctx.db.mutation.deleteCartItem({ where: { id: args.id }, }); @@ -260,6 +260,57 @@ const mutations = { // console.log(cartItem); // return cartItem; }, + async createOrder(parent, args, ctx, info) { + const userId = getUserId(ctx); + const user = await ctx.db.query.user( + { where: { id: userId } }, + '{ id, name, email, cart { id, quantity, item { price, id } }}' + ); + console.log('-------USER-----'); + console.log(user); + console.log('-------USER-----'); + // 1. Recalculate the total for the price + const amount = user.cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0); + // TODO Error Handling + // 2.1 Create a Stripe Customer + const customer = await stripe.customers.create({ + email: user.email, + }); + console.log(customer); + // 2.3 Charge the stripe token + const charge = await stripe.charges.create({ + amount, + currency: 'usd', + source: args.token, + }); + + // Save this order to the database + const items = user.cart.map(cartItem => ({ id: cartItem.item.id })); + console.l('---Gonna connect these items: ----'); + console.log(items); + console.l('---Gonna connect these items: ----'); + const order = await ctx.db.mutation.createOrder({ + data: { + total: charge.amount, + charge: charge.id, + items: { + connect: items, + }, + user: { + connect: { + id: user.id, + }, + }, + }, + }); + console.l('--------ORDER-------------'); + console.log(order); + console.l('--------ORDER-------------'); + return order; + // 4. Send an email with their order + // 5. Send the order back + // 6. Clean up, clear the users cart adn send back { user, order } + }, }; module.exports = mutations; diff --git a/backend/src/resolvers/Query.js b/backend/src/resolvers/Query.js index e6ea299..8649e43 100644 --- a/backend/src/resolvers/Query.js +++ b/backend/src/resolvers/Query.js @@ -31,6 +31,7 @@ const Query = { }, me(parent, args, ctx, info) { + console.l('me!'); const Authorization = ctx.request.get('Authorization'); if (!Authorization || Authorization === 'null') { console.log('Authorization is null'); @@ -39,6 +40,12 @@ const Query = { const id = getUserId(ctx); return ctx.db.query.user({ where: { id } }, info); }, + async orders(parent, args, ctx, info) { + console.l('--------------------FETCHING ORDERS---------------------'); + console.log(args); + return ctx.db.query.orders({}, info); + console.l('--------------------FETCHING ORDERS---------------------'); + }, }; module.exports = Query; diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index b30b096..84844c6 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -1,4 +1,4 @@ -# import CartItem, ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput from './generated/prisma.graphql' +# import Order, CartItem, ItemWhereInput, ItemOrderByInput, allItems, Post, Item, ItemCreateInput, ItemOrderByInput, ItemWhereInput, Query.order, Query.orders from './generated/prisma.graphql' type Query { feed: [Post!]! @@ -33,11 +33,12 @@ type Mutation { publish(id: ID!): Post! deletePost(id: ID!): Post! updatePost(id: ID!, title: String, text: String): Post! - createItem(title: String, description: String, price: Int): Item! + createItem(title: String, description: String, price: Int, image: String): Item! deleteItem(id: ID!): Item! updateItem(id: ID!, title: String, description: String, price: Int): Item! addToCart(id: ID!): CartItem removeFromCart(id: ID!): CartItem + createOrder(token: String!): Order! } type AuthPayload { @@ -52,5 +53,6 @@ type User { name: String! posts: [Post!]! age: Int + orders: [Order!]! cart: [CartItem!]! } diff --git a/backend/src/stripe.js b/backend/src/stripe.js new file mode 100644 index 0000000..c6be8ca --- /dev/null +++ b/backend/src/stripe.js @@ -0,0 +1 @@ +module.exports = require('stripe')(process.env.STRIPE_SECRET); diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js index 4522137..5a11513 100644 --- a/frontend/components/AddToCart.js +++ b/frontend/components/AddToCart.js @@ -38,7 +38,7 @@ const JumpImg = styled.img` class AddToCart extends Component { static propTypes = { - currentUser: PropTypes.object, + currentUser: PropTypes.object.isRequired, }; componentDidMount() { @@ -52,8 +52,7 @@ class AddToCart extends Component { id: this.props.id, }, }); - console.log({ realResponse: res }); - this.props.currentUser.refetch(); + // this.props.currentUser.refetch(); }; render() { diff --git a/frontend/components/Cart.js b/frontend/components/Cart.js index 4153600..46a8941 100644 --- a/frontend/components/Cart.js +++ b/frontend/components/Cart.js @@ -3,6 +3,8 @@ import { graphql, compose } from 'react-apollo'; import styled from 'styled-components'; import { CURRENT_USER_QUERY } from '../queries'; import RemoveFromCart from './RemoveFromCart'; +import TakeMyMoney from './TakeMyMoney'; +import { formatMoney } from '../lib/formatMoney'; const CartStyles = styled.div` background: white; @@ -43,6 +45,11 @@ class Cart extends Component { </li> ))} </ul> + + <TakeMyMoney> + <hr /> + <button>Checkout!!!</button> + </TakeMyMoney> {/* There {user.cart.length === 1 ? ' is ' : 'are '} <ChaChing amount={user.cart.length} /> diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js index e4aa77c..47e98ec 100644 --- a/frontend/components/CreateItem.js +++ b/frontend/components/CreateItem.js @@ -24,24 +24,27 @@ class CreateLink extends Component { uploadFile = async e => { this.setState({ loading: true }); + const files = e.currentTarget.files; const data = new FormData(); - data.append('data', files[0]); + data.append('file', files[0]); + data.append('upload_preset', 'sickfits'); // use the file endpoint - const res = await fetch(fileEndpoint, { + const res = await fetch('https://api.cloudinary.com/v1_1/wesbos/image/upload', { method: 'POST', body: data, }); const file = await res.json(); - this.setState({ image: file.id, loading: false }); + console.log(file); + this.setState({ image: file.secure_url, loading: false }); }; _createLink = async e => { e.preventDefault(); // pull the values from state - const { description, title, price } = this.state; + const { description, title, price, image } = this.state; // create a mutation // TODO: handle any errors // turn loading on @@ -53,6 +56,7 @@ class CreateLink extends Component { variables: { description, title, + image, price: parseInt(price), }, }); @@ -73,6 +77,7 @@ class CreateLink extends Component { <p> Image <input onChange={this.uploadFile} type="file" accept=".png, .jpg, .jpeg" /> + {this.state.image ? <img src={this.state.image} width="100" alt={this.state.title} /> : null} </p> <p> Title @@ -105,9 +110,11 @@ class CreateLink extends Component { ); } } + +// TODO: The create item should only be allowed + // 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. - export default graphql(CREATE_ITEM_MUTATION, { name: 'createItemMutation', options: { diff --git a/frontend/components/Header.js b/frontend/components/Header.js index 774bd0b..aa3c3d8 100644 --- a/frontend/components/Header.js +++ b/frontend/components/Header.js @@ -1,6 +1,6 @@ import { Component } from 'react'; import { graphql, compose } from 'react-apollo'; -import { CURRENT_USER_QUERY } from '../queries'; +import { userEnhancer } from '../enhancers/enhancers'; import Cart from './Cart'; import Login from './LoginAuth0'; import Search from './Search'; @@ -21,6 +21,7 @@ class Header extends Component { // this.props.currentUserQuery.refetch(); // This fetches the new data, and populates the user via props // setTimeout(this.props.currentUserQuery.refetch, 1); + console.log(this.props.currentUser); } render() { @@ -35,6 +36,5 @@ class Header extends Component { } } -const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUser' }); export default compose(userEnhancer)(Header); // export default Header; diff --git a/frontend/components/Item.js b/frontend/components/Item.js index 2a0fc69..42a6c87 100644 --- a/frontend/components/Item.js +++ b/frontend/components/Item.js @@ -5,7 +5,6 @@ import { compose } from 'react-apollo'; import { Link } from '../routes'; import AddToCart from './AddToCart'; import makeImage from '../lib/image'; -import TakeMyMoney from './TakeMyMoney'; import formatMoney from '../lib/formatMoney'; import { removeItemMutation } from '../enhancers/enhancers'; @@ -25,7 +24,7 @@ class ItemComponent extends React.Component { const item = this.props.item; return ( <Item key={item.id}> - {item.image ? <img key={item.image.secret} src={makeImage(item.image)} alt={item.title} /> : null} + {item.image ? <img src={item.image} alt={item.title} /> : null} <h3> <Link route="item" @@ -48,17 +47,6 @@ class ItemComponent extends React.Component { > <a>Edit ✏️</a> </Link> - - <TakeMyMoney - id={item.id} - amount={item.price} - name={item.title} // the pop-in header title - description={item.description} // the pop-in header subtitle - image={makeImage(item.image)} - > - <button>Buy for {formatMoney(item.price)}</button> - </TakeMyMoney> - <AddToCart id={item.id} /> <button onClick={this.removeItem}>× Delete item</button> </Item> diff --git a/frontend/components/Page.js b/frontend/components/Page.js index 291bdcd..93cd7c9 100644 --- a/frontend/components/Page.js +++ b/frontend/components/Page.js @@ -16,7 +16,7 @@ class ErrorBoundary extends Component { } render() { if (this.state.error) { - return <p>Shit! AN error!</p>; + return <p>Shit! AN error! {this.state.error.message}</p>; } return this.props.children; } diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js index 9d18074..47dbd0e 100644 --- a/frontend/components/SingleItem.js +++ b/frontend/components/SingleItem.js @@ -1,7 +1,6 @@ import { graphql, compose } from 'react-apollo'; import { SINGLE_ITEM_QUERY } from '../queries'; import { singleItemEnhancer } from '../enhancers/enhancers'; -import makeImage from '../lib/image'; const SingleItem = props => { if (props.loading || !props.item) return <p>Loading...</p>; @@ -10,7 +9,7 @@ const SingleItem = props => { const item = props.findItem.items[0]; return ( <div> - <img src={makeImage(item.image)} alt={item.title} /> + <img src={item.image} alt={item.title} /> <h2>Viewing {item.title}</h2> <p>{item.description}</p> </div> diff --git a/frontend/components/TakeMyMoney.js b/frontend/components/TakeMyMoney.js index 042e9c8..30a19dc 100644 --- a/frontend/components/TakeMyMoney.js +++ b/frontend/components/TakeMyMoney.js @@ -2,35 +2,47 @@ import { Component } from 'react'; import StripeCheckout from 'react-stripe-checkout'; import { graphql, compose } from 'react-apollo'; import { CREATE_ORDER_MUTATION, CURRENT_USER_QUERY } from '../queries'; +import formatMoney from '../lib/formatMoney'; class TakeMyMoney extends Component { onToken = async res => { console.log('We got a toooken!'); - const token = res.id; - const userId = this.props.currentUserQuery.user.id; - const itemId = this.props.id; - console.log(`Going to make a purchase with ${token}`); - console.log(`THe person that bought this was ${userId}`); - console.log(`The item id is ${itemId}`); - const charge = await this.props.createOrder({ variables: { token, userId, itemId } }); - alert(`Back from the charge! ${charge.id}`); - console.log(charge); + console.log(res); + const order = await this.props.createOrder({ + variables: { + token: res.id, + }, + }); + console.log(order); + // const token = res.id; + // const userId = this.props.currentUserQuery.user.id; + // const itemId = this.props.id; + // console.log(`Going to make a purchase with ${token}`); + // console.log(`THe person that bought this was ${userId}`); + // console.log(`The item id is ${itemId}`); + // const charge = await this.props.createOrder({ variables: { token, userId, itemId } }); + // alert(`Back from the charge! ${charge.id}`); + // console.log(charge); }; + render() { - // const user = this.props.currentUserQuery.user || {}; - // const email = user.email || ''; - const email = 'wesbos@gmail.com'; + const { me } = this.props.currentUserQuery; + if (!me.cart.length) return null; + const total = me.cart.reduce((tally, cartItem) => tally + cartItem.item.price * cartItem.quantity, 0); + const totalItems = me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0); return ( <div> <StripeCheckout - amount={this.props.amount} - name={this.props.name} - description={this.props.description} + amount={total} + name="Sick Fits Haul" + description={`Order of ${totalItems} Items From Sick Fits`} + image={me.cart[0].item.image} token={this.onToken} stripeKey="pk_lclTtThFp8CnO3QtEZSd8HA9mFUps" currency="USD" - email={email} + email={me.email} > + {formatMoney(total)} {this.props.children} </StripeCheckout> </div> @@ -40,4 +52,4 @@ class TakeMyMoney extends Component { const userEnhancer = graphql(CURRENT_USER_QUERY, { name: 'currentUserQuery' }); const createOrderEnhancer = graphql(CREATE_ORDER_MUTATION, { name: 'createOrder' }); -export default compose(createOrderEnhancer)(TakeMyMoney); +export default compose(userEnhancer, createOrderEnhancer)(TakeMyMoney); diff --git a/frontend/enhancers/enhancers.js b/frontend/enhancers/enhancers.js index f7021c3..1abdd78 100644 --- a/frontend/enhancers/enhancers.js +++ b/frontend/enhancers/enhancers.js @@ -59,7 +59,16 @@ export const addtoCartEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart', options: { update: (proxy, payload) => { - console.log('=----asdf-asd-fas-df-asdf-sa-df'); + const newCartItem = payload.data.addToCart; + const data = proxy.readQuery({ query: CURRENT_USER_QUERY }); + const existingIndex = data.me.cart.findIndex(cartItem => cartItem.id === newCartItem.id); + if (existingIndex >= 0) { + // already in cache, just replace it + data.me.cart = [...data.me.cart.slice(0, existingIndex), newCartItem, ...data.me.cart.slice(existingIndex + 1)]; + } else { + data.me.cart = [...data.me.cart, newCartItem]; + } + proxy.writeQuery({ query: CURRENT_USER_QUERY, data }); }, }, }); diff --git a/frontend/queries/fragments.js b/frontend/queries/fragments.js index 1708d78..9a2d641 100644 --- a/frontend/queries/fragments.js +++ b/frontend/queries/fragments.js @@ -6,5 +6,6 @@ export const itemDetails = gql` title price description + image } `; diff --git a/frontend/queries/index.js b/frontend/queries/index.js index e61a9d5..365c6f4 100644 --- a/frontend/queries/index.js +++ b/frontend/queries/index.js @@ -3,8 +3,8 @@ import { itemDetails } from './fragments'; export const CREATE_ITEM_MUTATION = gql` ${itemDetails} - mutation createItem($description: String!, $title: String!, $price: Int!) { - createItem(description: $description, title: $title, price: $price) { + mutation createItem($description: String!, $title: String!, $price: Int!, $image: String) { + createItem(description: $description, title: $title, price: $price, image: $image) { ...itemDetails } } @@ -58,20 +58,14 @@ export const RESET_MUTATION = gql` `; export const CREATE_ORDER_MUTATION = gql` - mutation CreateOrderMutation($token: String!, $userId: ID!, $itemId: ID!) { - createOrder(token: $token, userId: $userId, itemId: $itemId) { + mutation CreateOrderMutation($token: String!) { + createOrder(token: $token) { id - token charge - amount - item { + total + items { id title - description - } - user { - id - email } } } @@ -85,8 +79,6 @@ export const ALL_ITEMS_QUERY = gql` pageInfo { hasNextPage hasPreviousPage - startCursor - endCursor } aggregate { count @@ -161,6 +153,11 @@ export const CURRENT_USER_QUERY = gql` id email name + orders { + charge + id + total + } cart { id quantity @@ -200,6 +197,14 @@ export const ADD_TO_CART_MUTATION = gql` mutation addToCart($id: ID!) { addToCart(id: $id) { id + quantity + item { + id + price + description + image + title + } } } `; |
