├── Back-End ├── nodemon.json ├── src │ ├── api │ │ ├── Cart │ │ │ ├── seeCart │ │ │ │ ├── seeCart.graphql │ │ │ │ └── seeCart.js │ │ │ ├── deleteCart │ │ │ │ ├── deleteCart.graphql │ │ │ │ └── deleteCart.js │ │ │ ├── editCart │ │ │ │ ├── editCart.graphql │ │ │ │ └── editCart.js │ │ │ ├── addCart │ │ │ │ ├── addCart.graphql │ │ │ │ └── addCart.js │ │ │ └── Cart.js │ │ ├── User │ │ │ ├── me │ │ │ │ ├── me.graphql │ │ │ │ └── me.js │ │ │ ├── login │ │ │ │ ├── login.graphql │ │ │ │ └── login.js │ │ │ ├── User.js │ │ │ ├── editUser │ │ │ │ ├── editUser.graphql │ │ │ │ └── editUser.js │ │ │ └── createAccount │ │ │ │ ├── createAccount.graphql │ │ │ │ └── createAccount.js │ │ ├── Payment │ │ │ ├── deletePayment │ │ │ │ ├── deletePayment.graphql │ │ │ │ └── deletePayment.js │ │ │ ├── seePayment │ │ │ │ ├── seePayment.graphql │ │ │ │ └── seePayment.js │ │ │ ├── addPayment │ │ │ │ ├── addPayment.graphql │ │ │ │ └── addPayment.js │ │ │ └── Payment.js │ │ ├── Product │ │ │ ├── deleteProduct │ │ │ │ ├── deleteProduct.graphql │ │ │ │ └── deleteProduct.js │ │ │ ├── Upload │ │ │ │ ├── upload.graphql │ │ │ │ └── upload.js │ │ │ ├── Product.js │ │ │ ├── seeProduct │ │ │ │ ├── seeProduct.graphql │ │ │ │ └── seeProduct.js │ │ │ └── editProduct │ │ │ │ ├── editProduct.graphql │ │ │ │ └── editProduct.js │ │ ├── BuyList │ │ │ ├── seeBuyList │ │ │ │ ├── seeBuyList.graphql │ │ │ │ └── seeBuyList.js │ │ │ ├── addBuyList │ │ │ │ ├── addBuyList.graphql │ │ │ │ └── addBuyList.js │ │ │ └── BuyList.js │ │ └── models.graphql │ ├── env.js │ ├── utils.js │ ├── middlewares.js │ ├── server.js │ ├── Schema.js │ └── passport.js ├── .babelrc ├── package.json ├── .gitignore └── datamodel.prisma ├── Design ├── Page │ ├── 1_Main.md │ ├── 2_Login.md │ ├── 4_Admin.md │ ├── 5_Store.md │ ├── 7_Order.md │ ├── 8_MyPage.md │ ├── 3_SignUp.md │ └── 6_ProductView.md ├── img │ ├── Admin.png │ ├── Login.png │ ├── Main.png │ ├── MyPage.png │ ├── Order.png │ ├── SignUp.png │ ├── Store.png │ └── ProductView.png └── Function │ └── 1_functionDesign.md ├── Front-End ├── src │ ├── Routes │ │ ├── Auth │ │ │ ├── index.js │ │ │ ├── AuthQueries.js │ │ │ ├── AuthPresenter.js │ │ │ └── AuthContainer.js │ │ ├── Admin │ │ │ ├── index.js │ │ │ ├── AdminQueries.js │ │ │ └── AdminPresenter.js │ │ ├── Main │ │ │ ├── index.js │ │ │ ├── MainQueries.js │ │ │ ├── MainContainer.js │ │ │ └── MainPresenter.js │ │ ├── Store │ │ │ ├── index.js │ │ │ ├── StoreQueries.js │ │ │ ├── StorePresenter.js │ │ │ └── StoreContainer.js │ │ ├── Mypage │ │ │ ├── index.js │ │ │ ├── MyPageQueries.js │ │ │ └── MyPagePresenter.js │ │ ├── Payment │ │ │ ├── index.js │ │ │ ├── PaymentQueries.js │ │ │ ├── PaymentPresenter.js │ │ │ └── PaymentContainer.js │ │ └── Product │ │ │ ├── index.js │ │ │ └── ProductQueries.js │ ├── Components │ │ ├── SharedFunction.js │ │ ├── DaumAPI.js │ │ ├── Button.js │ │ ├── Loader.js │ │ ├── Input.js │ │ ├── header.js │ │ ├── Footer.js │ │ ├── ItemBox.js │ │ ├── SharedQueries.js │ │ ├── Routes.js │ │ ├── App.js │ │ ├── Navigator.js │ │ ├── StoreContent.js │ │ ├── SignUpForm.js │ │ ├── Icons.js │ │ ├── EditProduct.js │ │ ├── DaumPostCode.js │ │ ├── ProductTable.js │ │ └── ProductEditForm.js │ ├── Hooks │ │ └── useInput.js │ ├── Apollo │ │ ├── Client.js │ │ └── LocalState.js │ ├── index.js │ ├── Styles │ │ ├── Theme.js │ │ └── GlobalStyles.js │ └── Firebase │ │ └── index.js ├── public │ ├── favicon.ico │ ├── manifest.json │ ├── slick │ │ ├── slick.min.css │ │ └── slick-theme.min.css │ └── index.html ├── .gitignore ├── package.json └── README.md └── README.md /Back-End/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": "js graphql" 3 | } -------------------------------------------------------------------------------- /Design/Page/1_Main.md: -------------------------------------------------------------------------------- 1 | # Main Page 2 | 3 | -------------------------------------------------------------------------------- /Design/Page/2_Login.md: -------------------------------------------------------------------------------- 1 | # Login Page 2 | 3 | -------------------------------------------------------------------------------- /Design/Page/4_Admin.md: -------------------------------------------------------------------------------- 1 | # Admin Page 2 | 3 | -------------------------------------------------------------------------------- /Design/Page/5_Store.md: -------------------------------------------------------------------------------- 1 | # Store Page 2 | 3 | -------------------------------------------------------------------------------- /Design/Page/7_Order.md: -------------------------------------------------------------------------------- 1 | # Order Page 2 | 3 | -------------------------------------------------------------------------------- /Design/Page/8_MyPage.md: -------------------------------------------------------------------------------- 1 | # MyPage 2 | 3 | -------------------------------------------------------------------------------- /Back-End/src/api/Cart/seeCart/seeCart.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | seeCart: [Cart!]! 3 | } -------------------------------------------------------------------------------- /Design/Page/3_SignUp.md: -------------------------------------------------------------------------------- 1 | # SignUp Page 2 | 3 | -------------------------------------------------------------------------------- /Design/img/Admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/Admin.png -------------------------------------------------------------------------------- /Design/img/Login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/Login.png -------------------------------------------------------------------------------- /Design/img/Main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/Main.png -------------------------------------------------------------------------------- /Design/img/MyPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/MyPage.png -------------------------------------------------------------------------------- /Design/img/Order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/Order.png -------------------------------------------------------------------------------- /Design/img/SignUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/SignUp.png -------------------------------------------------------------------------------- /Design/img/Store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/Store.png -------------------------------------------------------------------------------- /Design/Page/6_ProductView.md: -------------------------------------------------------------------------------- 1 | # ProductView Page 2 | 3 | -------------------------------------------------------------------------------- /Design/img/ProductView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Design/img/ProductView.png -------------------------------------------------------------------------------- /Front-End/src/Routes/Auth/index.js: -------------------------------------------------------------------------------- 1 | import AppContainer from "./AuthContainer"; 2 | export default AppContainer; -------------------------------------------------------------------------------- /Back-End/src/api/Cart/deleteCart/deleteCart.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | deleteCart(id: [String!]!) : Boolean! 3 | } -------------------------------------------------------------------------------- /Back-End/src/api/Cart/editCart/editCart.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | editCart(id: String! count: Int!): Cart! 3 | } -------------------------------------------------------------------------------- /Front-End/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woochan94/REACT_ChanStyle/HEAD/Front-End/public/favicon.ico -------------------------------------------------------------------------------- /Front-End/src/Routes/Admin/index.js: -------------------------------------------------------------------------------- 1 | import AdminContainer from "./AdminContainer"; 2 | export default AdminContainer; -------------------------------------------------------------------------------- /Front-End/src/Routes/Main/index.js: -------------------------------------------------------------------------------- 1 | import MainContainer from "./MainContainer"; 2 | 3 | export default MainContainer; -------------------------------------------------------------------------------- /Front-End/src/Routes/Store/index.js: -------------------------------------------------------------------------------- 1 | import StoreContainer from "./StoreContainer"; 2 | export default StoreContainer; -------------------------------------------------------------------------------- /Back-End/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-transform-runtime"] 4 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/me/me.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | me: User! 3 | } 4 | 5 | type Mutation { 6 | me: User! 7 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Mypage/index.js: -------------------------------------------------------------------------------- 1 | import MyPageContainer from "./MyPageContainer"; 2 | export default MyPageContainer; -------------------------------------------------------------------------------- /Back-End/src/api/User/login/login.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | login(email: String!, password: String!): String! 3 | } 4 | -------------------------------------------------------------------------------- /Front-End/src/Routes/Payment/index.js: -------------------------------------------------------------------------------- 1 | import PaymentContainer from "./PaymentContainer"; 2 | export default PaymentContainer; -------------------------------------------------------------------------------- /Back-End/src/api/Payment/deletePayment/deletePayment.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | deletePayment(id: [String!]!) : Boolean! 3 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Product/index.js: -------------------------------------------------------------------------------- 1 | import ProductContainer from "./ProductContainer"; 2 | 3 | export default ProductContainer; -------------------------------------------------------------------------------- /Back-End/src/env.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | import path from "path"; 3 | 4 | dotenv.config({ path: path.resolve(__dirname, ".env")}); -------------------------------------------------------------------------------- /Back-End/src/utils.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET); 4 | -------------------------------------------------------------------------------- /Back-End/src/api/Product/deleteProduct/deleteProduct.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | deleteProduct ( 3 | id: String! 4 | ): Boolean! 5 | } -------------------------------------------------------------------------------- /Back-End/src/api/Payment/seePayment/seePayment.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | seePayment: [Payment!]! 3 | } 4 | 5 | type Mutation { 6 | seePayment: [Payment!]! 7 | } -------------------------------------------------------------------------------- /Back-End/src/api/BuyList/seeBuyList/seeBuyList.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | seeBuyList: [BuyList!]! 3 | } 4 | 5 | type Mutation { 6 | seeBuyList2(first: Int, skip: Int): [BuyList!]! 7 | } -------------------------------------------------------------------------------- /Back-End/src/middlewares.js: -------------------------------------------------------------------------------- 1 | export const isAuthenticated = (request) => { 2 | if(!request.user) { 3 | throw Error('You need to log in to perform this action'); 4 | } 5 | return; 6 | } -------------------------------------------------------------------------------- /Front-End/src/Components/SharedFunction.js: -------------------------------------------------------------------------------- 1 | export const addComma = (num) => { 2 | var regexp = /\B(?=(\d{3})+(?!\d))/g; // 1000의 자리마다 ,를 찍어주는 정규식 3 | return num.toString().replace(regexp, ','); 4 | } -------------------------------------------------------------------------------- /Back-End/src/api/BuyList/addBuyList/addBuyList.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | addBuyList( 3 | product: [String] 4 | size: [String] 5 | color: [String] 6 | quantity: [Int] 7 | ):Boolean! 8 | } -------------------------------------------------------------------------------- /Back-End/src/api/Cart/addCart/addCart.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | addCart( 3 | product: [String!] 4 | sizeId: [String!] 5 | colorId: [String!] 6 | stockId: [String!] 7 | count: [Int!] 8 | ):Boolean! 9 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/User.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../generated/prisma-client"; 2 | 3 | export default { 4 | User: { 5 | cart: ({ id }) => prisma.user({ id }).cart(), 6 | buyList: ({ id }) => prisma.user({ id }).buyList() 7 | } 8 | } -------------------------------------------------------------------------------- /Back-End/src/api/Payment/addPayment/addPayment.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | addPayment( 3 | product: [String!] 4 | size: [String!] 5 | color: [String!] 6 | stock: [String!] 7 | count: [Int!] 8 | cart: [String!] 9 | ):Boolean! 10 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/editUser/editUser.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | editUser( 3 | name: String 4 | zipCode: String 5 | address: String 6 | addressDetail: String 7 | phone: String 8 | password: String 9 | confirmPassword: String 10 | ): Boolean! 11 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/createAccount/createAccount.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | createAccount( 3 | name: String! 4 | email: String! 5 | password: String! 6 | zipCode: String! 7 | address: String! 8 | addressDetail: String! 9 | phone: String! 10 | ): Boolean! 11 | } -------------------------------------------------------------------------------- /Front-End/src/Hooks/useInput.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default defaultValue => { 4 | const [value, setValue] = useState(defaultValue); 5 | 6 | const onChange = e => { 7 | const { 8 | target: {value} 9 | } = e; 10 | setValue(value); 11 | } 12 | 13 | return { value, setValue, onChange }; 14 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Main/MainQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const MAIN_SEEITEM = gql` 4 | query seeproduct($sort: String!) { 5 | seeproduct(sort: $sort) { 6 | id 7 | name 8 | price 9 | files { 10 | id 11 | url 12 | } 13 | } 14 | } 15 | `; -------------------------------------------------------------------------------- /Front-End/src/Apollo/Client.js: -------------------------------------------------------------------------------- 1 | import ApolloClient from "apollo-boost"; 2 | import { defaults, resolvers } from "./LocalState"; 3 | 4 | export default new ApolloClient({ 5 | uri: "http://localhost:4000/", 6 | clientState: { 7 | defaults, 8 | resolvers 9 | }, 10 | headers: { 11 | Authorization: `Bearer ${localStorage.getItem("token")}` 12 | } 13 | }) -------------------------------------------------------------------------------- /Front-End/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /Front-End/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './Components/App'; 4 | import Client from "./Apollo/Client"; 5 | import { ApolloProvider } from "react-apollo-hooks"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById("root") 12 | ); 13 | 14 | -------------------------------------------------------------------------------- /Front-End/src/Components/DaumAPI.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PostCode from "./DaumPostCode"; 3 | 4 | export default ({ isOpen, handleAddress }) => { 5 | 6 | let dialog = () 7 | 8 | if(!isOpen) { 9 | dialog = null; 10 | } 11 | return ( 12 | <> 13 | {dialog} 14 | 15 | ) 16 | } -------------------------------------------------------------------------------- /Back-End/src/api/Product/Upload/upload.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | upload( 3 | name: String! 4 | price: Int! 5 | mainCategory: String! 6 | subCategory: String! 7 | files: [String!]! 8 | productDetailFiles: [String!] 9 | productSizeFiles: [String!] 10 | sizes: [String!]! 11 | colors: [String!]! 12 | stocks: [Int!]! 13 | ): Product! 14 | } -------------------------------------------------------------------------------- /Front-End/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env -------------------------------------------------------------------------------- /Back-End/src/api/BuyList/BuyList.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../generated/prisma-client"; 2 | 3 | export default { 4 | BuyList: { 5 | product: ({ id }) => prisma.buyList({ id }).product(), 6 | user: ({ id }) => prisma.buyList({ id }).user(), 7 | size: ({ id }) => prisma.buyList({ id }).size(), 8 | color: ({ id }) => prisma.buyList({ id }).color(), 9 | quantity: ({ id }) => prisma.buyList({ id }).quantity() 10 | } 11 | } -------------------------------------------------------------------------------- /Front-End/src/Styles/Theme.js: -------------------------------------------------------------------------------- 1 | const boxBorder = "1px solid #e6e6e6"; 2 | const borderRadius = "4px"; 3 | 4 | export default { 5 | maxWidth: "1000px", 6 | bgColor: "#FAFAFA", 7 | whiteColor: "#fff", 8 | boxBorder, 9 | borderRadius, 10 | borderBottom:"1px solid #a9a9a9", 11 | titleFontSize: "32px", 12 | confirmColor: "#2c3e50", 13 | whiteBox: `border-radius:${borderRadius}; 14 | border:${boxBorder}; 15 | background-color: white;` 16 | } -------------------------------------------------------------------------------- /Back-End/src/api/Cart/Cart.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../generated/prisma-client"; 2 | 3 | export default { 4 | Cart: { 5 | user: ({ id }) => prisma.cart({ id }).user(), 6 | product: ({ id }) => prisma.cart({ id }).product(), 7 | sizeId: ({ id }) => prisma.cart({ id }).sizeId(), 8 | colorId: ({ id }) => prisma.cart({ id }).colorId(), 9 | stockId: ({ id }) => prisma.cart({ id }).stockId(), 10 | count: ({ id }) => prisma.cart({ id }).count() 11 | } 12 | } -------------------------------------------------------------------------------- /Back-End/src/api/Product/deleteProduct/deleteProduct.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Mutation: { 5 | deleteProduct: async (_, args) => { 6 | const { id } = args; 7 | try { 8 | await prisma.deleteProduct({ 9 | id 10 | }); 11 | return true; 12 | } catch { 13 | return false; 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Back-End/src/api/Cart/seeCart/seeCart.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Query: { 5 | seeCart: (_, __, { request , isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { user } = request; 8 | return prisma.carts({ 9 | where: { 10 | user: { 11 | id: user.id 12 | } 13 | } 14 | }) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Back-End/src/api/Cart/editCart/editCart.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Mutation: { 5 | editCart: (_, args, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { id, count } = args; 8 | return prisma.updateCart({ 9 | where: { 10 | id 11 | }, 12 | data: { 13 | count 14 | } 15 | }) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Back-End/src/api/Product/Product.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../generated/prisma-client"; 2 | 3 | export default { 4 | Product: { 5 | files: ({ id }) => prisma.product({ id }).files(), 6 | sizes: ({ id }) => prisma.product({ id }).sizes(), 7 | colors: ({ id }) => prisma.product({ id }).colors(), 8 | stocks: ({ id }) => prisma.product({ id }).stocks(), 9 | productDetailFile: ({ id }) => prisma.product({ id }).productDetailFile(), 10 | productSizeFile: ({ id }) => prisma.product({ id }).productSizeFile() 11 | } 12 | } -------------------------------------------------------------------------------- /Back-End/src/api/Payment/Payment.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../generated/prisma-client"; 2 | 3 | export default { 4 | Payment: { 5 | user: ({ id }) => prisma.payment({ id }).user(), 6 | product: ({ id }) => prisma.payment({ id }).product(), 7 | size: ({ id }) => prisma.payment({ id }).size(), 8 | color: ({ id }) => prisma.payment({ id }).color(), 9 | stock: ({ id }) => prisma.payment({ id }).stock(), 10 | count: ({ id }) => prisma.payment({ id }).count(), 11 | cart: ({ id }) => prisma.payment({ id }).cart() 12 | } 13 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/me/me.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Query: { 5 | me:(_, __, { request, isAuthenticated}) => { 6 | isAuthenticated(request); 7 | const { user } = request; 8 | return prisma.user({ id: user.id }); 9 | } 10 | }, 11 | Mutation: { 12 | me:(_, __, { request, isAuthenticated}) => { 13 | isAuthenticated(request); 14 | const { user } = request; 15 | return prisma.user({ id: user.id }); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Back-End/src/server.js: -------------------------------------------------------------------------------- 1 | import "./env"; 2 | import { GraphQLServer } from "graphql-yoga"; 3 | import logger from "morgan"; 4 | import schema from "./Schema"; 5 | import "./passport"; 6 | import { authenticateJwt } from "./passport"; 7 | import { isAuthenticated } from './middlewares'; 8 | 9 | 10 | const server = new GraphQLServer({ 11 | schema, 12 | context: ({request}) => ({ request, isAuthenticated }) 13 | }); 14 | 15 | server.express.use(logger("dev")); 16 | server.express.use(authenticateJwt); 17 | 18 | server.start(() => console.log(`✅ Server is running on localhost:${process.env.PORT}`)); 19 | -------------------------------------------------------------------------------- /Back-End/src/api/Cart/deleteCart/deleteCart.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Mutation: { 5 | deleteCart: async (_, args, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { id } = args; 8 | try { 9 | await id.map(async(item) => { 10 | await prisma.deleteCart({ 11 | id: item 12 | }) 13 | }) 14 | return true; 15 | } catch { 16 | return false; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Back-End/src/api/Product/seeProduct/seeProduct.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | seeproduct( 3 | id: String 4 | sort: String 5 | mainCategory: String 6 | subCategory: String 7 | ): [Product!]! 8 | } 9 | 10 | type Mutation { 11 | seeProductBest( 12 | id: String 13 | sort: String! 14 | mainCategory: String 15 | subCategory: String 16 | ): [Product!]! 17 | } 18 | 19 | type Mutation { 20 | seeProductAll( 21 | id: String 22 | sort: String! 23 | mainCategory: String 24 | subCategory: String 25 | first: Int 26 | skip: Int 27 | ): [Product!]! 28 | } -------------------------------------------------------------------------------- /Back-End/src/api/Payment/deletePayment/deletePayment.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Mutation: { 5 | deletePayment: async(_, args, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { id } = args; 8 | try { 9 | await id.map(async(item) => { 10 | await prisma.deletePayment({ 11 | id: item 12 | }) 13 | }) 14 | return true; 15 | } catch { 16 | return false; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Front-End/src/Firebase/index.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase/app"; 2 | import 'firebase/storage'; 3 | 4 | // Your web app's Firebase configuration 5 | var firebaseConfig = { 6 | apiKey: process.env.REACT_APP_APIKEY, 7 | authDomain: process.env.REACT_APP_AUTHDOMAIN, 8 | databaseURL: process.env.REACT_APP_DATABASEURL, 9 | projectId: process.env.REACT_APP_PROJECTID, 10 | storageBucket: process.env.REACT_APP_STOREBUCKET, 11 | messagingSenderId: process.env.REACT_APP_MESSAGINGSENERID, 12 | appId: process.env.REACT_APP_APPID 13 | }; 14 | // Initialize Firebase 15 | firebase.initializeApp(firebaseConfig); 16 | 17 | const storage = firebase.storage(); 18 | 19 | export { storage, firebase as default }; -------------------------------------------------------------------------------- /Front-End/src/Apollo/LocalState.js: -------------------------------------------------------------------------------- 1 | export const defaults = { 2 | // localStorage에서 token의 유무에 따라 로그인 여부 판단해줌 3 | isLoggedIn: Boolean(localStorage.getItem("token")) || false 4 | } 5 | 6 | export const resolvers = { 7 | Mutation: { 8 | logUserIn: (_, { token }, { cache }) => { 9 | localStorage.setItem("token", token); 10 | cache.writeData({ 11 | data: { 12 | isLoggedIn: true 13 | } 14 | }); 15 | return null; 16 | }, 17 | logUserOut: (_, __, { cache }) => { 18 | localStorage.removeItem("token"); 19 | window.location.reload(); 20 | return null; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Front-End/src/Components/Button.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import PropTypes from "prop-types"; 4 | 5 | const Container = styled.button` 6 | width: 100%; 7 | border: 0; 8 | border-radius: ${props => props.theme.borderRadius}; 9 | text-align: center; 10 | cursor: pointer; 11 | padding: 10px; 12 | font-size: 16px; 13 | `; 14 | 15 | const Button = ({ text, onClick, type = "button", id }) => {text} 16 | 17 | Button.propTypes = { 18 | text: PropTypes.string.isRequired, 19 | onClick: PropTypes.func, 20 | type: PropTypes.string, 21 | id: PropTypes.string 22 | }; 23 | 24 | export default Button; -------------------------------------------------------------------------------- /Front-End/src/Styles/GlobalStyles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | import reset from "styled-reset"; 3 | 4 | export default createGlobalStyle` 5 | ${reset}; 6 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700'); 7 | * { 8 | box-sizing: border-box; 9 | } 10 | body { 11 | font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 12 | padding-top: 123px; 13 | background-color: ${props => props.theme.bgColor}; 14 | @media (max-width: 600px) { 15 | padding-top: 160px; 16 | } 17 | } 18 | a { 19 | text-decoration: none; 20 | color: black; 21 | } 22 | `; -------------------------------------------------------------------------------- /Front-End/src/Components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { keyframes } from "styled-components"; 3 | import { Logo } from "./Icons"; 4 | 5 | const Animation = keyframes` 6 | 0%{ 7 | opacity:0 8 | } 9 | 50%{ 10 | opacity:1 11 | } 12 | 100%{ 13 | opacity:0; 14 | } 15 | `; 16 | 17 | const Loader = styled.div` 18 | display: flex; 19 | animation: ${Animation} 1s linear infinite; 20 | width: 100%; 21 | justify-content: center; 22 | align-items: center; 23 | min-height: 76vh; 24 | @media (max-width: 768px) { 25 | min-height: 73vh; 26 | } 27 | @media (max-width: 600px) { 28 | min-height: 68vh; 29 | } 30 | @media (max-width: 480px) { 31 | min-height: 64vh; 32 | } 33 | `; 34 | 35 | export default () => ( 36 | 37 | 38 | 39 | ) -------------------------------------------------------------------------------- /Back-End/src/Schema.js: -------------------------------------------------------------------------------- 1 | // 모든 파일들을 이 파일(schema.js)에서 합칠것임 2 | import path from "path"; 3 | import {makeExecutableSchema}from "graphql-tools"; 4 | import { mergeTypes, mergeResolvers, fileLoader } from 'merge-graphql-schemas'; 5 | 6 | // fileLoader 함수를 사용하여 지정된 폴더에서 모든 파일을 가져온다. 7 | // fileLoader를 통해 typeDefs와 resolvers를 모두 가져올 것이다 (.graphql 파일, .js 파일) 8 | // 때문에 api 폴더 밑에는 resolver이나 graphql이 아닌 파일을 두면 안된다. 9 | // 예를 들어 resolver이 아닌 js파일이 api폴더 밑에 생성된다면 현재파일(schema.js)에서 그 js파일을 10 | // resolver로 인식하기 때문에 에러가 발생할 수 있음 11 | const allTypes = fileLoader(path.join(__dirname, "/api/**/*.graphql")); 12 | const allResolvers = fileLoader(path.join(__dirname, "/api/**/*.js")); 13 | 14 | // makeExecutableSchema를 이용해서 GraphQL Schema를 만들 수 있음 ( typeDefs, resolvers ) 15 | const schema = makeExecutableSchema({ 16 | typeDefs: mergeTypes(allTypes), 17 | resolvers: mergeResolvers(allResolvers) 18 | }); 19 | 20 | export default schema; -------------------------------------------------------------------------------- /Back-End/src/api/Payment/seePayment/seePayment.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Query: { 5 | seePayment: (_, __, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { user } = request; 8 | return prisma.payments({ 9 | where: { 10 | user: { 11 | id: user.id 12 | } 13 | } 14 | }) 15 | } 16 | }, 17 | Mutation: { 18 | seePayment: (_, __, { request, isAuthenticated}) => { 19 | isAuthenticated(request); 20 | const { user } = request; 21 | return prisma.payments({ 22 | where: { 23 | user: { 24 | id: user.id 25 | } 26 | } 27 | }) 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Store/StoreQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const SEE_ALL_BESTITEM = gql` 4 | mutation seeProductBest($sort: String!, $mainCategory: String, $subCategory: String) { 5 | seeProductBest(sort: $sort, mainCategory: $mainCategory, subCategory: $subCategory) { 6 | id 7 | name 8 | price 9 | files { 10 | id 11 | url 12 | } 13 | } 14 | } 15 | `; 16 | 17 | export const SEE_PRODUCT = gql` 18 | mutation seeProductAll($id: String, $sort: String!, $mainCategory: String, $subCategory: String, $first: Int, $skip: Int) { 19 | seeProductAll(id:$id, sort:$sort, mainCategory:$mainCategory, subCategory:$subCategory, first:$first, skip:$skip) { 20 | id 21 | name 22 | price 23 | files { 24 | id 25 | url 26 | } 27 | } 28 | } 29 | `; -------------------------------------------------------------------------------- /Front-End/src/Components/Input.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | 5 | const Container = styled.input` 6 | border: 0; 7 | border: ${props => props.theme.boxBorder}; 8 | border-radius: ${props => props.theme.borderRadius}; 9 | padding: 10px; 10 | `; 11 | 12 | const Input = ({ 13 | placeholder, 14 | required = true, 15 | value, 16 | onChange, 17 | type = "text", 18 | id, 19 | defaultValue 20 | }) => () 29 | 30 | Input.propTypes = { 31 | placeholder: PropTypes.string, 32 | require: PropTypes.bool, 33 | value: PropTypes.string, 34 | onChange: PropTypes.func, 35 | type: PropTypes.string, 36 | id: PropTypes.string 37 | } 38 | 39 | export default Input; -------------------------------------------------------------------------------- /Back-End/src/api/BuyList/seeBuyList/seeBuyList.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Query: { 5 | seeBuyList: (_, __, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { user } = request; 8 | return prisma.buyLists({ 9 | where: { 10 | user: { 11 | id: user.id 12 | } 13 | } 14 | }) 15 | } 16 | }, 17 | Mutation: { 18 | seeBuyList2: (_, args, { request, isAuthenticated }) => { 19 | isAuthenticated(request); 20 | const { user } = request; 21 | const { first, skip } = args; 22 | return prisma.buyLists({ 23 | where: { 24 | user: { 25 | id: user.id 26 | } 27 | }, 28 | first, 29 | skip 30 | }) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Auth/AuthQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const LOG_IN = gql` 4 | mutation login($email: String!, $password: String!) { 5 | login(email: $email, password: $password) 6 | } 7 | `; 8 | 9 | export const CREATE_ACCOUNT = gql` 10 | mutation createAccount( 11 | $name: String!, 12 | $email: String!, 13 | $password: String!, 14 | $zipCode: String!, 15 | $address: String!, 16 | $addressDetail: String!, 17 | $phone: String! 18 | ) { 19 | createAccount( 20 | name: $name, 21 | email: $email, 22 | password: $password, 23 | zipCode: $zipCode, 24 | address: $address, 25 | addressDetail: $addressDetail, 26 | phone: $phone 27 | ) 28 | } 29 | `; 30 | 31 | // LocalState에 작성한 Local용 mutation을 통해 token을 생성하는 query문 32 | export const LOCAL_LOG_IN = gql` 33 | mutation logUserIn($token: String!) { 34 | logUserIn(token: $token) @client 35 | } 36 | `; -------------------------------------------------------------------------------- /Front-End/src/Components/header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const Header = styled.header` 6 | width: 100%; 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | border-bottom: ${props => props.theme.boxBorder}; 11 | background-color: black; 12 | z-index: 9999; 13 | `; 14 | 15 | const HeaderWrapper = styled.div` 16 | width: 100%; 17 | max-width: ${props => props.theme.maxWidth}; 18 | margin: 0 auto; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | @media (max-width: 600px) { 23 | height:120px; 24 | } 25 | `; 26 | 27 | const Logo = styled.h1` 28 | padding: 25px 0; 29 | font-size: 32px; 30 | font-weight: 600; 31 | a { 32 | color: white; 33 | } 34 | `; 35 | 36 | export default () => { 37 | return ( 38 |
39 | 40 | 41 | CHANSTYLE 42 | 43 | 44 |
45 | ) 46 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/login/login.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | import { generateToken } from '../../../utils'; 3 | import bcrypt from 'bcrypt'; 4 | 5 | // 비밀번호 정규식 설정 (영문, 숫자, 특수문자 조합, 8~16자리) 6 | const chk_password = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{8,16}$/; 7 | 8 | export default { 9 | Mutation: { 10 | login: async (_, args) => { 11 | const { email, password } = args; 12 | const user = await prisma.user({ email }); 13 | if( user !== null ) { 14 | // 비밀번호 정규식 확인 15 | if (chk_password.test(password) === false) { 16 | throw Error("비밀번호는 영문, 숫자, 특수문자 조합의 8~16자리여야 합니다."); 17 | } 18 | // 비밀번호 암호화 확인 19 | if(bcrypt.compareSync(password, user.password)) { 20 | return generateToken(user.id); 21 | } else { 22 | throw Error("Wrong email/Password combination"); 23 | } 24 | } else { 25 | throw Error("존재하지 않는 이메일입니다."); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Back-End/src/passport.js: -------------------------------------------------------------------------------- 1 | import passport from "passport"; 2 | import { Strategy, ExtractJwt } from "passport-jwt"; 3 | import { prisma } from './../generated/prisma-client/'; 4 | 5 | const jwtOptions = { 6 | // Authorization 헤더에서 jwt를 찾는 역할 7 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 8 | secretOrKey: process.env.JWT_SECRET 9 | }; 10 | 11 | const verifyUser = async (payload, done) => { 12 | try { 13 | const user = await prisma.user({ id: payload.id }); 14 | if (user !== null) { 15 | return done(null, user); 16 | } else { 17 | return done(null, false); 18 | } 19 | } catch (err) { 20 | return done(err, false); 21 | } 22 | } 23 | 24 | export const authenticateJwt = (req, res, next) => passport.authenticate("jwt", { sessions: false }, (err, user) => { 25 | if(user) { 26 | // verifyUser에서 사용자를 받아온 후, 사용자 정보를 req객체에 붙여주는 역할 27 | req.user = user; 28 | } 29 | next(); 30 | })(req, res, next); 31 | 32 | // JWT는 토큰을 입력받아서 정보를 해석한 후 해석된 정보를 콜백 함수로 전달해 준다. 33 | // strategy가 모든 작업을 한 후 결과물을 payload에 전달해줌 34 | passport.use(new Strategy(jwtOptions, verifyUser)); 35 | passport.initialize(); -------------------------------------------------------------------------------- /Back-End/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Back-End", 3 | "version": "1.0.0", 4 | "description": "ChanStyle Backend with Prisma + GrpahQL + Express ", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "@babel/plugin-transform-runtime": "^7.5.5", 9 | "nodemon": "^1.19.1" 10 | }, 11 | "dependencies": { 12 | "@babel/cli": "^7.4.4", 13 | "@babel/core": "^7.4.5", 14 | "@babel/node": "^7.4.5", 15 | "@babel/preset-env": "^7.4.5", 16 | "@babel/runtime": "^7.5.5", 17 | "bcrypt": "^3.0.6", 18 | "copy": "^0.3.2", 19 | "dotenv": "^8.0.0", 20 | "graphql-tools": "^4.0.4", 21 | "graphql-yoga": "^1.17.4", 22 | "jsonwebtoken": "^8.5.1", 23 | "merge-graphql-schemas": "^1.5.8", 24 | "morgan": "^1.9.1", 25 | "passport": "^0.4.0", 26 | "passport-jwt": "^4.0.0", 27 | "prisma-client-lib": "^1.34.0" 28 | }, 29 | "scripts": { 30 | "deploy": "prisma deploy", 31 | "generate": "prisma generate", 32 | "prisma": "yarn run deploy && yarn run generate", 33 | "dev": "nodemon --exec babel-node src/server.js", 34 | "build": "babel src -d build", 35 | "postbuild": "cd src && npx copy ./api/**/*.graphql ../build/api/", 36 | "start" : "node build/server.js" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Front-End/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "front-end", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-boost": "^0.4.2", 7 | "firebase": "^6.2.4", 8 | "graphql": "^14.3.1", 9 | "prop-types": "^15.7.2", 10 | "react": "^16.8.6", 11 | "react-apollo-hooks": "^0.4.5", 12 | "react-dom": "^16.8.6", 13 | "react-helmet": "^5.2.1", 14 | "react-modal": "^3.8.1", 15 | "react-router-dom": "^5.0.1", 16 | "react-scripts": "3.0.1", 17 | "react-scroll": "^1.7.12", 18 | "react-slick": "^0.24.0", 19 | "react-toastify": "^5.3.0", 20 | "semantic-ui-react": "^0.87.2", 21 | "styled-components": "^4.3.1", 22 | "styled-reset": "^2.0.15", 23 | "underscore": "^1.9.1" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": "react-app" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Front-End/src/Components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Footer = styled.footer` 5 | display: flex; 6 | background-color: black; 7 | padding: 30px 0 10px; 8 | @media (max-width: 480px) { 9 | height: 23vh; 10 | } 11 | `; 12 | 13 | const FooterWrapper = styled.div` 14 | width: 100%; 15 | max-width: ${props => props.theme.maxWidth}; 16 | margin: 0 auto; 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | flex-direction: column; 21 | @media (max-width: 480px) { 22 | padding-bottom: 50px; 23 | } 24 | `; 25 | 26 | const GIT = styled.div` 27 | margin-bottom: 15px; 28 | a{ 29 | color: white; 30 | } 31 | svg { 32 | color: white; 33 | } 34 | `; 35 | 36 | 37 | 38 | const Copyright = styled.div` 39 | color: white; 40 | `; 41 | 42 | export default () => ( 43 |
44 | 45 | 46 | GitHub 바로가기 47 | 48 | Copyright © {new Date().getFullYear()}. All Rights Reserved. 49 | 50 |
51 | ) -------------------------------------------------------------------------------- /Front-End/src/Components/ItemBox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { Link } from "react-router-dom"; 4 | import { addComma } from "./SharedFunction"; 5 | 6 | export default ({ imgSrc, title, price, id }) => { 7 | const ItemDiv = styled.article` 8 | display: flex; 9 | flex-direction: column; 10 | margin: 0 10px; 11 | justify-content: space-between; 12 | `; 13 | 14 | const Img = styled.img` 15 | ${props => props.theme.whiteBox}; 16 | width: 100%; 17 | box-shadow: 0px 0px 0px rgba(0,0,0,0), 0px 0px 10px rgba(0,0,0,0.1); 18 | `; 19 | 20 | const TextDiv = styled.div` 21 | display: flex; 22 | flex-direction: column; 23 | margin-top: 10px; 24 | `; 25 | 26 | const P = styled.p` 27 | padding-bottom: 5px; 28 | color: #555555; 29 | font-size: 14px; 30 | font-weight: 600; 31 | `; 32 | 33 | 34 | 35 | return ( 36 | 37 | 38 | 39 | 40 |

{title}

41 |

₩{addComma(price)}

42 |
43 |
44 | 45 | ) 46 | } -------------------------------------------------------------------------------- /Back-End/src/api/Product/editProduct/editProduct.graphql: -------------------------------------------------------------------------------- 1 | type Mutation { 2 | editProduct ( 3 | id: String! 4 | name: String 5 | price: Int 6 | mainCategory: String 7 | subCategory: String 8 | ): Product! 9 | } 10 | 11 | type Mutation { 12 | editNumberOfSales ( 13 | id: [String!]! 14 | saleCount: [Int!]! 15 | ): Boolean! 16 | } 17 | 18 | type Mutation { 19 | editFile ( 20 | fileId: String 21 | file: [String] 22 | ): Boolean! 23 | } 24 | 25 | type Mutation { 26 | editSize ( 27 | productId: String 28 | sizeId: [String] 29 | sizeValue: [String] 30 | ): Boolean! 31 | } 32 | 33 | type Mutation { 34 | editColor ( 35 | productId: String 36 | colorId: [String] 37 | colorValue: [String] 38 | ): Boolean! 39 | } 40 | 41 | type Mutation { 42 | editStock ( 43 | productId: String 44 | stockId: [String] 45 | stockValue: [Int] 46 | ): Boolean! 47 | } 48 | 49 | type Mutation { 50 | editProductDetailFile ( 51 | id: String! 52 | productDetailFile: String! 53 | ): Product! 54 | } 55 | 56 | type Mutation { 57 | editProductSizeFile ( 58 | id: String! 59 | productSizeFile: String! 60 | ): Product! 61 | } -------------------------------------------------------------------------------- /Back-End/src/api/User/createAccount/createAccount.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | import bcrypt from "bcrypt"; 3 | 4 | // 비밀번호 정규식 설정 (영문, 숫자, 특수문자 조합, 8~16자리) 5 | const chk_password = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{8,16}$/; 6 | 7 | export default { 8 | Mutation: { 9 | createAccount: async (_, args) => { 10 | const { name, email, password, zipCode, address, addressDetail, phone } = args; 11 | // 이메일 중복확인을 위한 코드 12 | // 입력한 이메일이 이미 db에 있으면 true값을 반환 13 | const exists = await prisma.$exists.user({ email }); 14 | if (exists) { 15 | throw Error("This email is already taken"); 16 | } else { 17 | // 비밀번호 정규식 확인 18 | if (chk_password.test(password) === false) { 19 | throw Error("비밀번호는 영문, 숫자, 특수문자 조합의 8~16자리여야 합니다."); 20 | } 21 | // 비밀번호 암호화 진행 22 | bcrypt.hash(password, 10, async function(err, hash) { 23 | await prisma.createUser({ 24 | name, 25 | email, 26 | password: hash, 27 | zipCode, 28 | address, 29 | addressDetail, 30 | phone 31 | }); 32 | }); 33 | return true; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Front-End/src/Routes/Payment/PaymentQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const SEE_PAYMENT = gql` 4 | { 5 | seePayment { 6 | id 7 | product { 8 | id 9 | name 10 | price 11 | numberOfSales 12 | files { 13 | id 14 | url 15 | } 16 | } 17 | size { 18 | id 19 | size 20 | } 21 | color { 22 | id 23 | color 24 | } 25 | stock { 26 | id 27 | stock 28 | } 29 | count { 30 | id 31 | count 32 | } 33 | cart { 34 | id 35 | } 36 | } 37 | } 38 | `; 39 | 40 | export const EDIT_STOCK = gql` 41 | mutation editStock($id: [String!]!, $stock: [Int!]!) { 42 | editStock(id: $id, stock: $stock) 43 | } 44 | `; 45 | 46 | export const EDIT_NUMBEROFSALES = gql` 47 | mutation editNumberOfSales($id: [String!]!, $saleCount: [Int!]!) { 48 | editNumberOfSales(id: $id, saleCount: $saleCount) 49 | } 50 | ` 51 | 52 | export const ADD_BUYLIST = gql` 53 | mutation addBuyList($product: [String!]!, $size: [String!]!, $color: [String!]!, $quantity: [Int!]!) { 54 | addBuyList(product: $product, size: $size, color: $color, quantity: $quantity) 55 | } 56 | `; -------------------------------------------------------------------------------- /Front-End/src/Routes/Product/ProductQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const SEEITEM = gql` 4 | query seeproduct($id: String!) { 5 | seeproduct(id: $id) { 6 | id 7 | name 8 | price 9 | mainCategory 10 | subCategory 11 | files { 12 | id 13 | url 14 | } 15 | colors { 16 | id 17 | color 18 | } 19 | sizes { 20 | id 21 | size 22 | } 23 | stocks { 24 | id 25 | stock 26 | } 27 | productDetailFile { 28 | id 29 | productDetailFile 30 | } 31 | productSizeFile { 32 | id 33 | productSizeFile 34 | } 35 | } 36 | } 37 | `; 38 | 39 | export const ADD_CART = gql` 40 | mutation addCart($product: [String!]!, $sizeId: [String!]!, $colorId: [String!]!, $stockId: [String!]!, $count: [Int!]!) { 41 | addCart(product: $product, sizeId: $sizeId, colorId: $colorId, stockId: $stockId, count: $count) 42 | } 43 | ` 44 | 45 | export const ADD_PAYMENT = gql` 46 | mutation addPayment($product: [String!]!, $size: [String!]!, $color: [String!]!, $stock: [String!]!, $count: [Int!]!, $cart: [String!]) { 47 | addPayment(product: $product, size: $size, color: $color, stock: $stock, count: $count, cart: $cart) 48 | } 49 | `; -------------------------------------------------------------------------------- /Front-End/src/Components/SharedQueries.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const QUERY = gql` 4 | { 5 | isLoggedIn @client 6 | } 7 | `; 8 | 9 | export const ME = gql` 10 | { 11 | me { 12 | id 13 | name 14 | email 15 | zipCode 16 | address 17 | addressDetail 18 | phone 19 | } 20 | } 21 | `; 22 | 23 | export const MEMUTATION = gql` 24 | mutation me { 25 | me { 26 | id 27 | name 28 | email 29 | zipCode 30 | address 31 | addressDetail 32 | phone 33 | } 34 | } 35 | `; 36 | 37 | export const DELETE_PAYMENT = gql` 38 | mutation deletePayment($id: [String!]!) { 39 | deletePayment(id: $id) 40 | } 41 | `; 42 | 43 | export const SEE_PAYMENT2 = gql` 44 | mutation seePayment { 45 | seePayment { 46 | id 47 | product { 48 | id 49 | name 50 | price 51 | numberOfSales 52 | files { 53 | id 54 | url 55 | } 56 | } 57 | size { 58 | id 59 | size 60 | } 61 | color { 62 | id 63 | color 64 | } 65 | stock { 66 | id 67 | stock 68 | } 69 | count { 70 | id 71 | count 72 | } 73 | cart { 74 | id 75 | } 76 | } 77 | } 78 | ` -------------------------------------------------------------------------------- /Back-End/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | 90 | generated 91 | prisma.yml 92 | build -------------------------------------------------------------------------------- /Front-End/public/slick/slick.min.css: -------------------------------------------------------------------------------- 1 | .slick-list,.slick-slider,.slick-track{ 2 | position:relative; 3 | display:block; 4 | } 5 | 6 | .slick-loading .slick-slide,.slick-loading .slick-track{ 7 | visibility:hidden 8 | } 9 | 10 | .slick-slider{ 11 | box-sizing:border-box; 12 | -webkit-user-select:none; 13 | -moz-user-select:none; 14 | -ms-user-select:none; 15 | user-select:none; 16 | -webkit-touch-callout:none; 17 | -khtml-user-select:none; 18 | -ms-touch-action:pan-y; 19 | touch-action:pan-y; 20 | -webkit-tap-highlight-color:transparent 21 | } 22 | 23 | .slick-list{ 24 | overflow:hidden; 25 | margin:0; 26 | padding:0 27 | } 28 | 29 | .slick-list:focus{ 30 | outline:0 31 | } 32 | 33 | .slick-list.dragging{ 34 | cursor:pointer;cursor:hand 35 | } 36 | 37 | .slick-slider .slick-list,.slick-slider .slick-track{ 38 | -webkit-transform:translate3d(0,0,0); 39 | -moz-transform:translate3d(0,0,0); 40 | -ms-transform:translate3d(0,0,0); 41 | -o-transform:translate3d(0,0,0); 42 | transform:translate3d(0,0,0) 43 | } 44 | 45 | .slick-track{ 46 | top:0; 47 | left:0 48 | } 49 | 50 | .slick-track:after,.slick-track:before{ 51 | display:table;content:'' 52 | } 53 | 54 | .slick-track:after{ 55 | clear:both 56 | } 57 | 58 | .slick-slide{ 59 | display:none; 60 | float:left; 61 | height:100%; 62 | min-height:1px 63 | } 64 | 65 | [dir=rtl] .slick-slide{ 66 | float:right 67 | } 68 | 69 | .slick-slide img{ 70 | display:block 71 | } 72 | 73 | .slick-slide.slick-loading img{ 74 | display:none 75 | } 76 | 77 | .slick-slide.dragging img{ 78 | pointer-events:none 79 | } 80 | 81 | .slick-initialized .slick-slide{ 82 | display:block 83 | } 84 | 85 | .slick-vertical .slick-slide{ 86 | display:block; 87 | height:auto; 88 | border:1px solid transparent; 89 | } 90 | 91 | .slick-arrow.slick-hidden{ 92 | display:none 93 | }/*# sourceMappingURL=slick.min.css.map */ -------------------------------------------------------------------------------- /Back-End/src/api/User/editUser/editUser.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | import bcrypt from "bcrypt"; 3 | 4 | // 비밀번호 정규식 설정 (영문, 숫자, 특수문자 조합, 8~16자리) 5 | const chk_password = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{8,16}$/; 6 | 7 | export default { 8 | Mutation: { 9 | editUser: async (_, args, { request, isAuthenticated }) => { 10 | isAuthenticated(request); 11 | const { name, zipCode, address, addressDetail, phone, password, confirmPassword } = args; 12 | 13 | const { user } = request; 14 | 15 | if(password !== "" && confirmPassword !== "" && password === confirmPassword) { 16 | if(chk_password.test(password) === false) { 17 | throw Error("비밀번호는 영문, 숫자, 특수문자 조합의 8~16자리여야 합니다."); 18 | } else { 19 | bcrypt.hash(password, 10, async function(err, hash) { 20 | await prisma.updateUser({ 21 | where: { 22 | id: user.id 23 | }, 24 | data: { 25 | name, 26 | zipCode, 27 | address, 28 | addressDetail, 29 | phone, 30 | password: hash 31 | } 32 | }) 33 | }) 34 | return true; 35 | } 36 | } else { 37 | await prisma.updateUser({ 38 | where: { 39 | id: user.id 40 | }, 41 | data: { 42 | name, zipCode, address, addressDetail, phone 43 | } 44 | }); 45 | return true; 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Design/Function/1_functionDesign.md: -------------------------------------------------------------------------------- 1 | # 1_functionDesign 2 | 3 | - 초기 기능 구상 ( 2019. 06. 12 ) 4 | 5 |
6 | 7 | ## 1. 회원가입 및 로그인 8 | ### 1.1 회원가입 9 | ``` 10 | ▶ 이메일 11 | - 비동기 중복체크 12 | ▶ 비밀번호 13 | - 암호화 14 | ▶ 이름 15 | ▶ 주소 16 | - 주소검색 api 17 | ``` 18 | 19 | ### 1.2 로그인 20 | ``` 21 | ▶ 이메일 22 | ▶ 비밀번호 23 | ``` 24 |
25 | 26 | ## 2. 헤더 27 | ### 2.1 메뉴바 28 | ``` 29 | ▶ Logo 30 | ▶ Home 31 | ▶ Store 32 | ▶ Sale 33 | ▶ Login or MyPage 34 | - 로그인 여부에 따라 바뀌게끔 35 | ``` 36 | 37 |
38 | 39 | ## 3. 메인페이지 40 | ### 3.1 이미지 슬라이더 41 | ``` 42 | sticky 사용 43 | ``` 44 | ### 3.2 베스트 상품 45 | ### 3.3 신상품 46 | ### 3.4 Footer 47 | 48 |
49 | 50 | ## 4. 스토어 51 | ### 4.1 좌측 카테고리 52 | ``` 53 | ▶ 상의 54 | - All 55 | - 티셔츠 56 | - 셔츠 57 | ▶ 하의 58 | - All 59 | - 청바지 60 | - 슬랙스 61 | ``` 62 | ### 4.2 상품 View 63 | ``` 64 | ▶ 상품 정보 65 | - 상품 이름 66 | - 판매가 67 | - 남은 재고 68 | ▶ 옵션 선택 69 | - 색상 70 | - 사이즈 71 | - 수량 72 | ▶ 장바구니 담기 73 | - 로그인 상태에서만 가능 74 | - 비로그인 일시 로그인화면으로 이동 75 | ▶ 바로구매 76 | - 장바구니와 동일 77 | ▶ 제품상세 78 | - 제품 상세 79 | - 사이즈 기준표 80 | - 판매자 정보 81 | ``` 82 | 83 |
84 | 85 | ## 5. 마이페이지 86 | ### 5.1 개인정보 수정 87 | ``` 88 | ▶ 비밀번호 수정 89 | ▶ 주소 수정 90 | ``` 91 | ### 5.2 장바구니 92 | ``` 93 | ▶ 장바구니 내 상품 합계 94 | ▶ 체크박스 형식 95 | - 체크된 상품들만 총 합계에 포함 96 | ▶ 수량변경 97 | ``` 98 | ### 5.3 구매목록 99 | ### 5.4 로그아웃 100 | 101 |
102 | 103 | ## 6. 관리자페이지 104 | ### 6.1 상품관리 105 | ``` 106 | ▶ 등록 107 | - 제품 이미지 108 | - 제품명 109 | - 제품 분류 110 | - 제품 가격 111 | - 제품 사이즈 (toggle Button 이용) 112 | - 재고량 113 | - 제품 상세 ( 상세 설명(생략), 사이즈표, 판매자 정보) 114 | ▶ 수정 115 | ▶ 삭제 116 | ▶ 관리 117 | - 제품별 재고량 체크 할수 있는 Component 118 | ``` 119 | 120 |
121 | 122 | ## 7. 결제 123 | ### 7.1 결제페이지 124 | ``` 125 | ▶ 구매 상품 정보 126 | ▶ 주문자 정보 127 | ``` 128 | ### 7.2 결제 129 | ``` 130 | ▶ 아이엠포트 (결제모듈) 이용 131 | ``` -------------------------------------------------------------------------------- /Back-End/src/api/Cart/addCart/addCart.js: -------------------------------------------------------------------------------- 1 | import { prisma } from "../../../../generated/prisma-client"; 2 | 3 | export default { 4 | Mutation: { 5 | addCart: async (_, args, { request, isAuthenticated }) => { 6 | isAuthenticated(request); 7 | const { user } = request; 8 | const { product, sizeId, colorId, stockId, count } = args; 9 | try { 10 | await count.map(async(item, index) => { 11 | const countId = await prisma.createCount({ 12 | count: item 13 | }); 14 | await prisma.createCart({ 15 | user: { 16 | connect: { 17 | id: user.id 18 | } 19 | }, 20 | product: { 21 | connect: { 22 | id: product[index] 23 | } 24 | }, 25 | sizeId: { 26 | connect: { 27 | id: sizeId[index] 28 | } 29 | }, 30 | colorId: { 31 | connect: { 32 | id: colorId[index] 33 | } 34 | }, 35 | stockId: { 36 | connect: { 37 | id: stockId[index] 38 | } 39 | }, 40 | count: { 41 | connect: { 42 | id: countId.id 43 | } 44 | } 45 | }) 46 | }) 47 | return true; 48 | } catch(err) { 49 | console.log(err); 50 | return false; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Front-End/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Front-End/src/Components/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Route, Switch, withRouter } from "react-router-dom"; 4 | import Main from "../Routes/Main"; 5 | import MyPage from "../Routes/Mypage"; 6 | import Auth from "../Routes/Auth"; 7 | import Store from "../Routes/Store"; 8 | import Product from "../Routes/Product"; 9 | import Payment from "../Routes/Payment"; 10 | import Admin from "../Routes/Admin"; 11 | import { useMutation } from "react-apollo-hooks"; 12 | import { DELETE_PAYMENT, SEE_PAYMENT2 } from "./SharedQueries"; 13 | 14 | const AppRouter = withRouter(({ isLoggedIn, isAdmin, location }) => { 15 | let idArrayTemp = []; 16 | 17 | const deletePaymentMutation = useMutation(DELETE_PAYMENT, { variables: { 18 | id:idArrayTemp 19 | }}) 20 | 21 | const seePaymentMutation = useMutation(SEE_PAYMENT2); 22 | 23 | const seePaymentFunction = async() => { 24 | const { data } = await seePaymentMutation(); 25 | if(data) { 26 | data.seePayment.map(item => ( 27 | idArrayTemp.push(item.id) 28 | )) 29 | deletePaymentFunction(); 30 | } 31 | } 32 | 33 | const deletePaymentFunction = async () => { 34 | const { data } = await deletePaymentMutation(); 35 | if(data) { 36 | idArrayTemp = []; 37 | } 38 | } 39 | 40 | // 다른 페이지로 이동할 때마다 payment필드에 값이 있으면 제거한다. 41 | useEffect(() => { 42 | if(isLoggedIn && location.pathname !== "/payment") { 43 | seePaymentFunction(); 44 | } 45 | // eslint-disable-next-line react-hooks/exhaustive-deps 46 | },[location]) 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ) 56 | }) 57 | 58 | // Router는 항상 proptypes를 가져야 함 59 | AppRouter.propTypes = { 60 | isLoggedIn: PropTypes.bool.isRequired 61 | } 62 | 63 | export default AppRouter; -------------------------------------------------------------------------------- /Front-End/src/Components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ThemeProvider } from "styled-components"; 3 | import GlobalStyles from "../Styles/GlobalStyles"; 4 | import Theme from '../Styles/Theme'; 5 | import Routes from './Routes'; 6 | import { HashRouter as Router } from "react-router-dom"; 7 | import { useQuery, useMutation } from 'react-apollo-hooks'; 8 | import { ToastContainer, toast } from 'react-toastify'; 9 | import "react-toastify/dist/ReactToastify.css"; 10 | import Header from "./Header"; 11 | import Footer from './Footer'; 12 | import Navigator from './Navigator'; 13 | import { QUERY, MEMUTATION } from './SharedQueries'; 14 | import { useState } from 'react'; 15 | import { useEffect } from 'react'; 16 | 17 | export default () => { 18 | const [isAdmin, setIsAdmin] = useState(false); 19 | const [loading, setLoading] = useState(false); 20 | 21 | const { 22 | data: { isLoggedIn } 23 | } = useQuery(QUERY); 24 | 25 | const meMutation = useMutation(MEMUTATION); 26 | const meFunction = async () => { 27 | const { data } = await meMutation(); 28 | if (data.me.email === process.env.REACT_APP_ADMIN) { 29 | setIsAdmin(true); 30 | } 31 | setLoading(true); 32 | } 33 | 34 | useEffect(() => { 35 | if(isLoggedIn) { 36 | meFunction(); 37 | } 38 | // eslint-disable-next-line react-hooks/exhaustive-deps 39 | }, [isLoggedIn]) 40 | 41 | 42 | return ( 43 | 44 | <> 45 | 46 | 47 | {isLoggedIn && loading && ( 48 | <> 49 |
50 | 51 | 52 |