├── .DS_Store
├── .github
└── workflows
│ └── nodejs.yml
├── .travis.yml
├── .vscode
└── settings.json
├── README.md
├── auction-server
├── .env.example
├── .gitignore
├── Dockerfile
├── index.js
├── mailService.js
├── package-lock.json
└── package.json
├── chat-server
├── .gitignore
├── Dockerfile
├── README.md
├── index.js
├── package-lock.json
└── package.json
├── client
├── .env.example
├── .firebaserc
├── .gitignore
├── .storybook
│ ├── addons.js
│ └── config.js
├── README.md
├── database.rules.json
├── firebase.json
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── src
│ ├── App.js
│ ├── App.test.js
│ ├── Router.jsx
│ ├── assets
│ │ ├── cloth.svg
│ │ ├── default-profile.svg
│ │ ├── fail.svg
│ │ ├── fbShareIcon.svg
│ │ ├── font
│ │ │ ├── NanumGothic-Regular.ttf
│ │ │ └── NanumGothic-Regular.woff
│ │ ├── found.png
│ │ ├── geek.svg
│ │ ├── kakaoShareIcon.svg
│ │ ├── letmessengerbutton.svg
│ │ ├── loading.gif
│ │ ├── logo.svg
│ │ ├── messenger.svg
│ │ ├── notFound.png
│ │ ├── notify.svg
│ │ ├── report.svg
│ │ ├── settings.svg
│ │ ├── success.svg
│ │ ├── television.svg
│ │ ├── tickets.svg
│ │ ├── twitterShareIcon.svg
│ │ └── urlShareIcon.svg
│ ├── components
│ │ ├── Atoms
│ │ │ ├── BoxButton
│ │ │ │ └── index.jsx
│ │ │ ├── BoxHeader
│ │ │ │ └── index.jsx
│ │ │ ├── BtnListItem
│ │ │ │ └── index.jsx
│ │ │ ├── Card
│ │ │ │ └── index.jsx
│ │ │ ├── DayButton
│ │ │ │ └── index.jsx
│ │ │ ├── Footer
│ │ │ │ └── index.jsx
│ │ │ ├── Header
│ │ │ │ └── index.jsx
│ │ │ ├── InputWithLimit
│ │ │ │ └── index.jsx
│ │ │ ├── LoadingBar
│ │ │ │ └── index.jsx
│ │ │ ├── NotiNumber
│ │ │ │ └── index.jsx
│ │ │ ├── RatingButton
│ │ │ │ └── index.jsx
│ │ │ ├── ReportButton
│ │ │ │ └── index.jsx
│ │ │ ├── SelectOptionButton
│ │ │ │ └── index.jsx
│ │ │ ├── SmallCard
│ │ │ │ └── index.jsx
│ │ │ ├── Spinner
│ │ │ │ └── index.jsx
│ │ │ ├── TextTimer
│ │ │ │ └── index.jsx
│ │ │ ├── TextareaWithLength
│ │ │ │ └── index.jsx
│ │ │ └── ToggleButton
│ │ │ │ └── index.jsx
│ │ ├── Messenger
│ │ │ ├── Container
│ │ │ │ ├── ChatContainer
│ │ │ │ │ ├── ChatMessage.jsx
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── RoomElement
│ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── CreateButton
│ │ │ │ └── index.jsx
│ │ │ └── index.jsx
│ │ ├── Molecules
│ │ │ ├── AlertDialog
│ │ │ │ └── index.jsx
│ │ │ ├── CardContainer
│ │ │ │ └── index.jsx
│ │ │ ├── Carousel
│ │ │ │ ├── AddButton
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── BeforeButton
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── CarouselImage
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── NextButton
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── RemoveButton
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── constant.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── CustomModal
│ │ │ │ ├── FailModal.jsx
│ │ │ │ ├── LoginModal.jsx
│ │ │ │ ├── Modal.jsx
│ │ │ │ ├── ReportModal.jsx
│ │ │ │ ├── SignUpModal.jsx
│ │ │ │ ├── SuccessModal.jsx
│ │ │ │ ├── UserModalCommonStyles.jsx
│ │ │ │ └── constant.jsx
│ │ │ ├── InfiniteScroll
│ │ │ │ ├── index.jsx
│ │ │ │ └── useIntersect.jsx
│ │ │ ├── MoneyBox
│ │ │ │ └── index.jsx
│ │ │ ├── NotifyList
│ │ │ │ ├── NotifyItem.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── RatingDialog
│ │ │ │ └── index.jsx
│ │ │ ├── SelectBox
│ │ │ │ ├── List
│ │ │ │ │ └── index.jsx
│ │ │ │ ├── ListItem
│ │ │ │ │ └── index.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── ShareBox
│ │ │ │ └── index.jsx
│ │ │ ├── SmallCardContainer
│ │ │ │ └── index.jsx
│ │ │ ├── TitleListBox
│ │ │ │ └── index.jsx
│ │ │ ├── TradeBase
│ │ │ │ └── index.jsx
│ │ │ └── UserInfoBox
│ │ │ │ └── index.jsx
│ │ └── Organism
│ │ │ ├── AuctionGraph
│ │ │ └── index.jsx
│ │ │ ├── CategoryBar
│ │ │ ├── CategoryIcon
│ │ │ │ ├── ImageIcon.jsx
│ │ │ │ ├── TextIcon.jsx
│ │ │ │ └── index.jsx
│ │ │ ├── ExpandList
│ │ │ │ └── index.jsx
│ │ │ ├── LoginButton
│ │ │ │ └── index.jsx
│ │ │ ├── Logo
│ │ │ │ └── index.jsx
│ │ │ ├── Profile
│ │ │ │ └── index.jsx
│ │ │ └── index.jsx
│ │ │ ├── Chat
│ │ │ ├── Chat.jsx
│ │ │ ├── ChatBox.jsx
│ │ │ └── ChatSend.jsx
│ │ │ ├── ItemCategorySelector
│ │ │ └── index.jsx
│ │ │ ├── ProductInfo
│ │ │ └── index.jsx
│ │ │ ├── RegisterProgress
│ │ │ ├── Button
│ │ │ │ └── index.jsx
│ │ │ └── index.jsx
│ │ │ ├── RegisterTermSelector
│ │ │ └── index.jsx
│ │ │ ├── TradeBox
│ │ │ ├── constants.jsx
│ │ │ └── index.jsx
│ │ │ └── TradeListBox
│ │ │ └── index.jsx
│ ├── config
│ │ ├── api.js
│ │ ├── firebase.js
│ │ └── path.js
│ ├── constants
│ │ ├── strings.js
│ │ └── values.js
│ ├── context
│ │ ├── MessengerContext.js
│ │ ├── ModalContext.js
│ │ ├── NotificationContext.js
│ │ ├── ProductPageContext.js
│ │ ├── SocketContext.js
│ │ └── UserContext.js
│ ├── data
│ │ └── detail-category-list.js
│ ├── hooks
│ │ ├── useFetch.js
│ │ ├── usePrevious.js
│ │ └── useSocket.js
│ ├── index.js
│ ├── mock
│ │ ├── deadline-items
│ │ │ └── deadline-items.js
│ │ ├── index.jsx
│ │ ├── myitems
│ │ │ └── myitems.js
│ │ └── popular-items
│ │ │ └── popular-items.js
│ ├── pages
│ │ ├── CategoryItems
│ │ │ └── index.jsx
│ │ ├── ErrorPage
│ │ │ └── index.jsx
│ │ ├── Main
│ │ │ └── index.jsx
│ │ ├── MyItems
│ │ │ ├── contants.jsx
│ │ │ └── index.jsx
│ │ ├── Product
│ │ │ └── index.jsx
│ │ ├── ProductUpdate
│ │ │ ├── constants.jsx
│ │ │ └── index.jsx
│ │ ├── Register
│ │ │ ├── constants.jsx
│ │ │ ├── context.jsx
│ │ │ ├── index.jsx
│ │ │ └── template
│ │ │ │ ├── Complete
│ │ │ │ └── index.jsx
│ │ │ │ ├── InsertInfo
│ │ │ │ └── index.jsx
│ │ │ │ └── SelectCategory
│ │ │ │ └── index.jsx
│ │ └── TradeList
│ │ │ └── index.jsx
│ ├── serviceWorker.js
│ ├── services
│ │ ├── fetchService.js
│ │ └── imageService.js
│ ├── shared
│ │ └── firebase.js
│ ├── stories
│ │ ├── 3-Progress.stories.js
│ │ ├── 5-Carousel.stories.js
│ │ ├── 6-SelectBox.stories.js
│ │ ├── Atoms
│ │ │ ├── BoxButton.stories.js
│ │ │ ├── Card.stories.js
│ │ │ ├── Color.stories.js
│ │ │ ├── Footer.stories.js
│ │ │ ├── Header.stories.js
│ │ │ ├── InputWithLimits.stories.js
│ │ │ ├── LoadingBar.stories.js
│ │ │ ├── Padding.stories.js
│ │ │ ├── Text.stories.js
│ │ │ ├── TextareaWithLength.stories.js
│ │ │ └── ToggleButton.stories.js
│ │ ├── Modal
│ │ │ ├── AlretDialog.stories.js
│ │ │ └── ReportDialog.stories.js
│ │ ├── Molecules
│ │ │ ├── InfiniteScroll.stories.js
│ │ │ ├── ShareButtonBox.stories.js
│ │ │ ├── TermSelector.stories.js
│ │ │ └── TradeResultBox.stories.js
│ │ ├── Organisms
│ │ │ ├── CategoryBar.stories.js
│ │ │ ├── Chats.stories.js
│ │ │ ├── Messenger.stories.js
│ │ │ └── Products.stories.js
│ │ └── TradeList.stories.js
│ ├── style
│ │ ├── App.css
│ │ └── index.css
│ └── utils
│ │ ├── converter.js
│ │ ├── dateUtil.js
│ │ ├── fetchUtil.js
│ │ └── validator.js
└── yarn.lock
├── docker-compose.yml
└── server
├── .env.example
├── .gitignore
├── Dockerfile
├── README.md
├── ormconfig.js
├── package-lock.json
├── package.json
├── src
├── app.ts
├── config
│ ├── firestore.ts
│ ├── key.ts
│ └── objectStorage.ts
├── constants
│ ├── category.json
│ └── oauthAPIs.ts
├── controllers
│ └── api
│ │ ├── BidContorller.ts
│ │ ├── ItemController.ts
│ │ ├── LogController.ts
│ │ ├── LoginController.ts
│ │ ├── ProductController.ts
│ │ ├── StaticController.ts
│ │ ├── StorageController.ts
│ │ └── UserController.ts
├── custom
│ └── CustomNamingStrategy.ts
├── database
│ ├── factories
│ │ ├── BidFactory.ts
│ │ ├── ImageFactory.ts
│ │ ├── ProductFactory.ts
│ │ └── UserFactory.ts
│ └── seeds
│ │ ├── BidSeed.ts
│ │ └── UserSeed.ts
├── dto
│ ├── BidResponseDTO.ts
│ ├── ImageDTO.ts
│ ├── ImageResponseDTO.ts
│ ├── ProductCardResponseDTO.ts
│ ├── ProductDTO.ts
│ ├── ProductResponseDTO.ts
│ ├── UserDTO.ts
│ └── UserResponseDTO.ts
├── middlewares
│ └── SystemLogger.ts
├── models
│ ├── Bids.ts
│ ├── Images.ts
│ ├── Products.ts
│ └── Users.ts
├── repositories
│ ├── BidRepository.ts
│ ├── ImageRepository.ts
│ ├── LogRepository.ts
│ ├── ProductRepository.ts
│ └── UserRepository.ts
├── server.ts
├── services
│ ├── BidService.ts
│ ├── FireStoreService.ts
│ ├── ItemService.ts
│ ├── LogService.ts
│ ├── ProductService.ts
│ ├── S3Service.ts
│ └── UserService.ts
└── util
│ ├── DateUtils.ts
│ ├── StringUtils.ts
│ ├── authUtils.ts
│ └── fetchUtil.ts
└── tsconfig.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/.DS_Store
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: yarn script test (install, build, test)
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v1
9 | - uses: borales/actions-yarn@v1.19.1
10 | with:
11 | cmd: install # will run `yarn install` command
12 | - uses: borales/actions-yarn@v1.19.1
13 | with:
14 | cmd: build # will run `yarn build` command
15 | - uses: borales/actions-yarn@v1.19.1
16 | with:
17 | cmd: test # will run `yarn test` command
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - 12.13
5 |
6 | branches:
7 | only:
8 | - master
9 | - develop
10 |
11 | jobs:
12 | include:
13 | - state: "Build Client"
14 | name: "Build Client"
15 | before_script:
16 | - cd client
17 | - yarn
18 | script:
19 | - yarn build
20 | - state: "Build Server"
21 | name: "Build Server"
22 | before_script:
23 | - cd server
24 | - npm install
25 | script:
26 | - npm build
27 |
28 | jobs:
29 | include:
30 | - state: "Build Chat"
31 | name: "Build Chat"
32 | before_script:
33 | - cd chat-server
34 | script:
35 | - npm start
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/auction-server/.env.example:
--------------------------------------------------------------------------------
1 | DB_TYPE=
2 | DB_HOST=
3 | DB_DOCKER_COMPOSE_SERVICE_HOST=
4 | DB_PORT=
5 | DB_USER=
6 | DB_PASSWORD=
7 | DB_NAME=
8 |
9 | CHAT_SERVER=
10 | CHAT_DEV_SERVER=
11 |
12 |
13 | ### Mail
14 | MAIL_ID=
15 | MAIL_PASSWORD=
16 | MAIL_BASE=
--------------------------------------------------------------------------------
/auction-server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_STORE
3 | .env
4 | dist
--------------------------------------------------------------------------------
/auction-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 | WORKDIR /app
3 |
4 | RUN npm install -g pm2
5 | RUN npm install -g cross-env
6 |
7 | COPY package*.json ./
8 | RUN npm install
9 |
10 | COPY . .
11 | EXPOSE 5000
12 | CMD [ "npm", "start"]
--------------------------------------------------------------------------------
/auction-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auction-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "cross-env NODE_ENV=production pm2-runtime index.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "axios": "^0.19.0",
15 | "dot-env": "0.0.1",
16 | "dotenv": "^8.2.0",
17 | "moment": "^2.24.0",
18 | "moment-timezone": "^0.5.27",
19 | "mysql2": "^2.0.2",
20 | "node-cron": "^2.0.3",
21 | "socket.io-client": "^2.3.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/chat-server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_STORE
3 | .env
4 | dist
--------------------------------------------------------------------------------
/chat-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 | WORKDIR /app
3 |
4 | RUN npm install -g pm2
5 | RUN npm install -g cross-env
6 |
7 | COPY package*.json ./
8 | RUN npm install
9 |
10 | COPY . .
11 | EXPOSE 4000
12 | CMD [ "npm", "start"]
--------------------------------------------------------------------------------
/chat-server/README.md:
--------------------------------------------------------------------------------
1 | ## chat-serer 개발 서버 실행 절차
2 |
3 | ```bash
4 | docker-compose up -d chat-server
5 | ```
6 |
--------------------------------------------------------------------------------
/chat-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "pm2-runtime index.js",
8 | "prod": "pm2 start index.js",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "express": "^4.17.1",
15 | "socket.io": "^2.3.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/client/.env.example:
--------------------------------------------------------------------------------
1 | # Develop
2 | ## Api
3 | REACT_APP_DEV_CLIENT=http://localhost
4 | REACT_APP_DEV_API=http://localhost:3000
5 | REACT_APP_DEV_CHAT=http://localhost:4000
6 | REACT_APP_DEV_KAKAO_KEY=
7 |
8 | ## Firebase
9 | REACT_APP_DEV_API_KEY= YOUR-API-KEY
10 | REACT_APP_DEV_AUTH_DOMAIN= YOUR-AUTH-KEY
11 | REACT_APP_DEV_DATABASE_URL= YOUR-AUTH-DATABASE-URL
12 | REACT_APP_DEV_PROJECT_ID= YOUR-PROJECT-ID
13 | REACT_APP_DEV_STORAGE_BUCKET= YOUR-STORAGE-BUCK
14 | REACT_APP_DEV_MESSAGING_SENDERID= YOUR-MESSAGING-SENDERID
15 | REACT_APP_DEV_APP_ID= YOUR-APP-ID
16 | REACT_APP_DEV_MEASUREMENT_ID= YOUR-MEASUREMENT-ID
17 |
18 | ## OAuth
19 | REACT_APP_DEV_OAUTH_KAKAO_KEY=
20 | REACT_APP_DEV_OAUTH_GOOGLE_KEY=
21 |
22 | # Production
23 | ## Api
24 | REACT_APP_CLIENT=
25 | REACT_APP_API=
26 | REACT_APP_CHAT=
27 | REACT_APP_KAKAO_KEY=
28 |
29 | ## Firebase
30 | REACT_APP_API_KEY=
31 | REACT_APP_AUTH_DOMAIN=
32 | REACT_APP_DATABASE_URL=
33 | REACT_APP_PROJECT_ID=
34 | REACT_APP_STORAGE_BUCKET=
35 | REACT_APP_MESSAGING_SENDERID=
36 | REACT_APP_APP_ID=
37 | REACT_APP_MEASUREMENT_ID=
38 |
39 | ### OAuth
40 | REACT_APP_OAUTH_KAKAO_KEY=
41 | REACT_APP_OAUTH_GOOGLE_KEY=
--------------------------------------------------------------------------------
/client/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "palda-df880"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/client/.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 | .env
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | /.vscode
--------------------------------------------------------------------------------
/client/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/client/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | // automatically import all files ending in *.stories.js
4 | configure(require.context('../src/stories', true, /\.stories\.js$/), module);
5 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Here is Palda Client-Server
2 |
3 | ## How to Start
4 | ```bash
5 | cd client
6 | yarn install
7 | yarn start
8 | ```
9 |
10 | ## Storybook
11 | if you want to show components
12 | ```bash
13 | yarn storybook
14 | ```
15 |
16 | ## Notice
17 | - 서비스를 정상적으로 사용하기 위해서는 server의 readme를 먼저 참고하시기 바랍니다.
18 | - 가급적 Server 설치 이후 사용하셔야 합니다.
19 | - Local이 아닌 타 사용자와 테스팅을 위해서는 데모 페이지를 이용하시기 바랍니다.
20 | - Auction 기능을 사용하기 위해서는 Auction-server가 가동되고 있어야 합니다.
21 |
22 | ---
23 | Readme last updated : 2019 11 21
24 |
--------------------------------------------------------------------------------
/client/database.rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | ".read": true,
4 | ".write": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/client/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "database": {
3 | "rules": "database.rules.json"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.6.0",
7 | "@material-ui/icons": "^4.5.1",
8 | "axios": "^0.19.0",
9 | "firebase": "^7.5.0",
10 | "moment": "^2.24.0",
11 | "moment-timezone": "^0.5.27",
12 | "react": "^16.11.0",
13 | "react-copy-to-clipboard": "^5.0.2",
14 | "react-dom": "^16.11.0",
15 | "react-google-login": "^5.0.7",
16 | "react-image-file-resizer": "^0.2.1",
17 | "react-kakao-login": "^1.2.0",
18 | "react-router-dom": "^5.1.2",
19 | "react-scripts": "3.2.0",
20 | "react-spinners": "^0.6.1",
21 | "react-tooltip": "^3.11.1",
22 | "recharts": "^1.8.5",
23 | "socket.io-client": "^2.3.0",
24 | "styled-components": "^4.4.1"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "build:dev": "react-scripts --max_old_space_size=768 build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject",
32 | "storybook": "start-storybook -p 8888 -s public",
33 | "build-storybook": "build-storybook -s public"
34 | },
35 | "eslintConfig": {
36 | "extends": "react-app"
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "@storybook/addon-actions": "^5.2.6",
52 | "@storybook/addon-links": "^5.2.6",
53 | "@storybook/addons": "^5.2.6",
54 | "@storybook/react": "^5.2.6"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
23 |
24 |
25 | No.1 중고 경매사이트 팔다 입니다.
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/public/logo512.png
--------------------------------------------------------------------------------
/client/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { BrowserRouter } from "react-router-dom";
3 | import Router from "./Router";
4 | import UserContext from "./context/UserContext";
5 | import "./style/App.css";
6 | import ModalContext from "./context/ModalContext";
7 | import { Modal } from "../src/components/Molecules/CustomModal/Modal";
8 | import NotificationContext from "./context/NotificationContext";
9 | import MessengerContext from "./context/MessengerContext";
10 | import SocketContext from "./context/SocketContext";
11 | import { useSocket } from "./hooks/useSocket";
12 |
13 | function App() {
14 | const [user, setUser] = useState({});
15 | const [modal, setModal] = useState({
16 | isOpen: false,
17 | component: null,
18 | props: {}
19 | });
20 | const [notifications, setNotifications] = useState([]);
21 | const [messengerOpen, setMessengerOpen] = useState(false);
22 | const { socket } = useSocket(user, setNotifications);
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
33 | {modal.isOpen ? : null}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default App;
47 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/client/src/Router.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Switch, Route } from "react-router-dom";
3 |
4 | import Main from "./pages/Main";
5 | import TradeList from "./pages/TradeList";
6 | import CategoryBar from "./components/Organism/CategoryBar";
7 | import Register from "./pages/Register";
8 | import ProductPage from "./pages/Product/index";
9 | import MyItem from "./pages/MyItems";
10 | import CategoryItems from "./pages/CategoryItems";
11 | import ProductUpdate from "./pages/ProductUpdate";
12 |
13 | import ErrorPage from "./pages/ErrorPage";
14 |
15 | const Router = () => {
16 | return (
17 | <>
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | >
31 | );
32 | };
33 |
34 | export default Router;
35 |
--------------------------------------------------------------------------------
/client/src/assets/cloth.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/client/src/assets/default-profile.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/client/src/assets/fail.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/assets/fbShareIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/assets/font/NanumGothic-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/src/assets/font/NanumGothic-Regular.ttf
--------------------------------------------------------------------------------
/client/src/assets/font/NanumGothic-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/src/assets/font/NanumGothic-Regular.woff
--------------------------------------------------------------------------------
/client/src/assets/found.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/src/assets/found.png
--------------------------------------------------------------------------------
/client/src/assets/kakaoShareIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/assets/letmessengerbutton.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
53 |
--------------------------------------------------------------------------------
/client/src/assets/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/src/assets/loading.gif
--------------------------------------------------------------------------------
/client/src/assets/notFound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connect-foundation/2019-11/099efa2433ec6c46de86290ef5d747f2492194a1/client/src/assets/notFound.png
--------------------------------------------------------------------------------
/client/src/assets/settings.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
57 |
--------------------------------------------------------------------------------
/client/src/assets/success.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/assets/tickets.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
66 |
--------------------------------------------------------------------------------
/client/src/assets/twitterShareIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/BoxButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Button = styled.button`
5 | width: 10rem;
6 | height: 4rem;
7 | padding: 0.5rem 0.25rem;
8 | border: #bfbfbf solid 1px;
9 | background: None;
10 | font-size: 1.5rem;
11 | box-sizing: border-box;
12 | outline: none;
13 |
14 | transition: border 0.2s ease-in-out;
15 |
16 | &:hover,
17 | &:focus {
18 | border: var(--color-primary) solid 2px;
19 | border-radius: 10px;
20 | }
21 | `
22 |
23 | const Component = ({ onClick, text }) => {
24 | return
25 | }
26 |
27 | export default Component
28 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/BoxHeader/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | width: 100%;
6 | height: 2.5rem;
7 | box-sizing: border-box;
8 | padding: 0.15rem 0.5rem;
9 | background-color: var(--color-primary);
10 | color: white;
11 | display: flex;
12 | justify-items: center;
13 | `
14 |
15 | const Title = styled.span`
16 | width: 100%;
17 | height: fit-content;
18 | font-size: 1.3rem;
19 | font-weight: 400;
20 | margin: auto;
21 | letter-spacing: 0.5rem;
22 | `
23 |
24 | const Components = props => {
25 | return (
26 |
27 | {props.text}
28 |
29 | )
30 | }
31 |
32 | export default Components
33 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/BtnListItem/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Button = styled.button`
5 | font-family: D2Coding, "D2 coding", monosapce;
6 | width: 100%;
7 | font-size: 1rem;
8 | background: ${props => (props.selected ? "var(--color-gray-lighter-plus)" : "white")};
9 | padding: 0.5em;
10 | text-align: left;
11 | border-bottom: var(--color-gray) solid 1px;
12 | outline: none;
13 |
14 | &:hover {
15 | background: ${props => (props.selected ? "var(--color-gray-darker)" : "whitesmoke")};
16 | }
17 | `
18 |
19 | const Component = props => {
20 | const { selected, onClick } = props
21 |
22 | const handle = e => onClick()
23 |
24 | return (
25 |
28 | )
29 | }
30 |
31 | export default Component
32 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/DayButton/index.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import React, { useState } from "react"
3 | const Ul = styled.ul`
4 | list-style: none;
5 | padding: 0 0 0 0;
6 | margin: 0 0 0 0;
7 | height: 2rem;
8 | `
9 | const Li = styled.li`
10 | display: list-item;
11 | list-style: none;
12 | margin: 0 0 0 0;
13 | padding: 0 0 0 0;
14 | border: 0;
15 | float: right;
16 | `
17 |
18 | const ButtonDay = styled.button`
19 | all: unset;
20 | background-color: ${props =>
21 | props.state ? "var(--color-secondary)" : "var(--color-secondary-minus1)"};
22 | text-align: center;
23 | width: 3rem;
24 | height: 2rem;
25 | margin: 0 0.1rem 0 0.1rem;
26 | &:hover {
27 | cursor: pointer;
28 | }
29 | &:active {
30 | background-color: var(--color-secondary);
31 | }
32 | `
33 |
34 | function ButtonDays(props) {
35 | const [day, setDay] = useState(true)
36 | const [week, setWeek] = useState(false)
37 | const [year, setYear] = useState(false)
38 | const [year3, setYear3] = useState(false)
39 |
40 | function checkday() {
41 | props.select_1d()
42 | setDay(true)
43 | setWeek(false)
44 | setYear(false)
45 | setYear3(false)
46 | }
47 |
48 | function checkweek() {
49 | props.select_1w()
50 | setDay(false)
51 | setWeek(true)
52 | setYear(false)
53 | setYear3(false)
54 | }
55 |
56 | function checkyear() {
57 | props.select_1y()
58 | setDay(false)
59 | setWeek(false)
60 | setYear(true)
61 | setYear3(false)
62 | }
63 |
64 | function checkyear3() {
65 | props.select_3y()
66 | setDay(false)
67 | setWeek(false)
68 | setYear(false)
69 | setYear3(true)
70 | }
71 |
72 | return (
73 |
74 | -
75 |
76 | 3년
77 |
78 |
79 | -
80 |
81 | 1년
82 |
83 |
84 | -
85 |
86 | 1주일
87 |
88 |
89 | -
90 |
91 | 1일
92 |
93 |
94 |
95 | )
96 | }
97 |
98 | export default ButtonDays
99 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/Footer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | width: 100%;
8 | height: 5rem;
9 | background: var(--color-quaternary);
10 | text-align: center;
11 | justify-content: center;
12 |
13 | font-family: "BMDOHYEON";
14 | color: white;
15 | `
16 |
17 | const Copyright = styled.div`
18 | font-size: 0.8rem;
19 | font-weight: 700;
20 | `
21 |
22 | const Component = () => {
23 | return (
24 |
25 | Copyright 2019. Team Palda, All right reserved
26 |
27 | )
28 | }
29 |
30 | export default Component
31 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | width: 100%;
9 | height: 5rem;
10 | text-align: left;
11 | box-sizing: border-box;
12 | align-items: flex-start;
13 | padding: 0.5rem;
14 | `
15 |
16 | const Text = styled.span`
17 | font-size: var(--font-size-xxl);
18 | font-weight: 700;
19 | color: var(--color-secondary);
20 | `
21 |
22 | const Header = ({ text }) => {
23 | return (
24 |
25 | {text}
26 |
27 | )
28 | }
29 |
30 | export default Header
31 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/InputWithLimit/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | width: 100%;
6 | padding: var(--padding-sm) var(--padding-md);
7 | box-sizing: border-box;
8 | display: flex;
9 | border-radius: 10px;
10 | outline: none;
11 | border: ${props => (props.exceed ? "var(--color-danger)" : "var(--color-gray)")} solid 1.5px;
12 | justify-content: space-between;
13 |
14 | transition: border 0.15s ease-in-out;
15 | &:focus-within {
16 | border-color: ${props =>
17 | props.exceed ? "var(--color-danger)" : "var(--color-primary-minus0)"};
18 | }
19 | `;
20 |
21 | const NonBorder = styled.input`
22 | font-family: "BMJUA";
23 | width: 90%;
24 | height: fit-content;
25 | font-size: var(--font-size-xl);
26 | outline: none;
27 | `;
28 |
29 | const Counter = styled.div`
30 | width: fit-content;
31 | font-size: var(--font-size-xs);
32 | color: ${props => (props.exceed ? "var(--color-danger)" : "var(--color-gray)")};
33 | `;
34 |
35 | const Component = ({ limit, hint, onChange, value, isBlockMode }) => {
36 | const [focus, setFocus] = useState(false);
37 | const [length, setLength] = useState(value.length);
38 |
39 | const handleOnChange = e => {
40 | const content = e.target.value;
41 | const validContent =
42 | isBlockMode && content.length > limit ? content.substring(0, limit) : content;
43 | onChange(validContent);
44 | setLength(validContent.length);
45 | };
46 |
47 | const handleBlock = e => {
48 | const keyCode = e.keyCode;
49 | const value = e.target.value;
50 | if (keyCode !== 8 && keyCode !== 46 && value.length >= limit) e.preventDefault();
51 | };
52 |
53 | return (
54 | limit}>
55 | setFocus(true)}
58 | onBlur={e => setFocus(false)}
59 | onChange={handleOnChange}
60 | onKeyDown={isBlockMode ? handleBlock : undefined}
61 | value={value}
62 | />
63 | limit}>{`${length} / ${limit}`}
64 |
65 | );
66 | };
67 |
68 | export default Component;
69 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/LoadingBar/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | display: flex;
6 | width: fit-content;
7 | height: 30px;
8 | `;
9 |
10 | const Content = styled.div`
11 | font-family: "BMJUA";
12 | display: flex;
13 | width: 60%;
14 | min-width: 400px;
15 | color: white;
16 | border-radius: 30px;
17 | background-color: var(--color-tertiary-plus0);
18 | align-items: center;
19 | justify-content: center;
20 | `;
21 |
22 | const Component = () => {
23 | return (
24 |
25 |
26 | Loading...
27 |
28 |
29 | );
30 | };
31 |
32 | export default Component;
33 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/NotiNumber/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from "react";
2 | import styled from "styled-components";
3 | import NotificationContext from "../../../context/NotificationContext";
4 | import { usePrevious } from "../../../hooks/usePrevious";
5 |
6 | const NotiNumberStyle = styled.div`
7 | position: absolute;
8 | top: 4px;
9 | right: 4px;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | width: 16px;
14 | height: 17px;
15 | font-size: 0.8rem;
16 | border-radius: 50%;
17 | background-color: red;
18 | border: 1px solid red;
19 | color: white;
20 | z-index: 1;
21 | font-weight: bold;
22 | `;
23 |
24 | const NotiNumber = props => {
25 | const { active } = props;
26 | const [notifications] = useContext(NotificationContext);
27 | const prevCount = usePrevious(notifications.length);
28 |
29 | if (active) {
30 | return <>>;
31 | }
32 |
33 | if (prevCount < notifications.length) {
34 | return N;
35 | }
36 |
37 | return <>>;
38 | };
39 |
40 | export default NotiNumber;
41 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/RatingButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import RatingDialog from "../../Molecules/RatingDialog";
4 |
5 | const Button = styled.button`
6 | margin: 0 var(--margin-xs);
7 | padding: var(--padding-xs);
8 | font-size: 0.5rem;
9 | font-weight: bold;
10 | color: var(--color-primary);
11 | border-radius: 16px;
12 | display: inline-block;
13 | border: 1px solid var(--color-primary);
14 | background-color: white;
15 |
16 | &:hover {
17 | color: white;
18 | background-color: var(--color-primary);
19 | cursor: pointer;
20 | }
21 | `;
22 | const ButtonWrap = styled.div`
23 | display: inline-block;
24 | height: 2rem;
25 | margin: 0;
26 | `;
27 | /**
28 | * 해당 유저에대해 평가 진행
29 | *
30 | * userid : number
31 | * targetId : id
32 | */
33 | const Component = props => {
34 | const [show, setShow] = useState(false);
35 | function RatingWrite() {
36 | setShow(!show);
37 | }
38 | return (
39 |
40 |
41 | {show ? (
42 |
49 | ) : (
50 | undefined
51 | )}
52 |
53 | );
54 | };
55 |
56 | export default Component;
57 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/ReportButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 | import ModalContext from "../../../context/ModalContext";
4 | import ReportModal from "../../Molecules/CustomModal/ReportModal";
5 |
6 | const Button = styled.button`
7 | margin: 0 var(--margin-xs);
8 | padding: var(--padding-xs);
9 | font-size: 0.5rem;
10 | font-weight: bold;
11 | color: var(--color-primary);
12 | border-radius: 16px;
13 | display: inline-block;
14 | border: 1px solid var(--color-primary);
15 | background-color: white;
16 |
17 | &:hover {
18 | color: white;
19 | background-color: var(--color-primary);
20 | cursor: pointer;
21 | }
22 | `;
23 | const ButtonWrap = styled.div`
24 | display: inline-block;
25 | height: 2rem;
26 | margin: 0;
27 | `;
28 | /**
29 | * 유저여부와, 해당 id를 입력하여 신고하기 버튼 제작
30 | *
31 | * isUser : boolean
32 | * targetId : id
33 | */
34 | const Component = props => {
35 | const [modal, setModal] = useContext(ModalContext);
36 | function ReportWrite() {
37 | return setModal({
38 | isOpen: true,
39 | component: ReportModal,
40 | props: { userId: props.userId, productId: props.productId }
41 | });
42 | }
43 | return (
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default Component;
51 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/SelectOptionButton/index.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 | import React, { useState } from "react"
3 |
4 | const ButtonSB = styled.button`
5 | all: unset;
6 | background-color: ${props =>
7 | props.state ? "var(--color-secondary)" : "var(--color-secondary-minus1)"};
8 | text-align: center;
9 | width: 3rem;
10 | height: 2rem;
11 | margin: 0 0.1rem 0 0.1rem;
12 | border-radius: 0.5rem;
13 | &:hover {
14 | cursor: pointer;
15 | }
16 | `
17 |
18 | function ButtonSelect(props) {
19 | const [select, setSelect] = useState(true)
20 | function clickbutton() {
21 | props.select()
22 | if (select) setSelect(false)
23 | else setSelect(true)
24 | }
25 | return (
26 |
27 | {props.name}
28 |
29 | )
30 | }
31 |
32 | export default ButtonSelect
33 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/SmallCard/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const smallCardSize = "9em";
5 |
6 | const SmallCardStyle = styled.div`
7 | display: flex;
8 | height: ${smallCardSize};
9 | width: ${smallCardSize};
10 | margin: 1rem;
11 | border-radius: 1rem;
12 | overflow: hidden;
13 | cursor: pointer;
14 | box-shadow: 0 0.1rem 0.4rem 0 rgba(0, 0, 0, 0.2),
15 | 0 0.3rem 0.2rem 0 rgba(0, 0, 0, 0.19);
16 | transition: all 0.15s ease-in-out;
17 | img {
18 | width: 100%;
19 | height: 100%;
20 | object-fit: cover;
21 | }
22 |
23 | &:hover {
24 | transform: scale(1.05);
25 | }
26 | `;
27 | const Link = styled.a`
28 | height: ${smallCardSize};
29 | `;
30 |
31 | const SmallCard = ({ item }) => {
32 | const { id, thumbnailUrl } = item;
33 | const link = `/products/${id}`;
34 | return (
35 |
36 |
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default SmallCard;
44 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/Spinner/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { ClipLoader } from "react-spinners";
4 |
5 | const SpinnerStyle = styled.div`
6 | display: flex;
7 | justify-content: center;
8 | align-items: center;
9 | width: 100%;
10 | height: 100%;
11 | flex-direction: column;
12 | `;
13 | const SpinnerText = styled.div`
14 | color: var(--color-primary);
15 | font-size: var(--font-size-xl);
16 | font-weight: bold;
17 | text-align: center;
18 | margin-bottom: var(--margin-md);
19 | `;
20 |
21 | const Spinner = ({ text }) => {
22 | return (
23 |
24 | {text || "Loading.."}
25 |
26 |
27 | );
28 | };
29 |
30 | export default Spinner;
31 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/TextTimer/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import styled from "styled-components";
3 | import { getDiffDateTime } from "../../../utils/dateUtil";
4 |
5 | const TextTimerStyle = styled.span``;
6 |
7 | const TextTimer = ({ datetime }) => {
8 | const [deadLine, setDeadLine] = useState(`시간 계산중`);
9 |
10 | useEffect(() => {
11 | if (!datetime) return;
12 | const timer = setInterval(() => {
13 | const { diff, d, h, m, s } = getDiffDateTime(datetime);
14 |
15 | if (diff > 0) {
16 | setDeadLine(`D-${d} ${h}:${m}:${s}`);
17 | } else {
18 | clearInterval(timer);
19 | setDeadLine(`종료`);
20 | }
21 | }, 1000);
22 | return () => {
23 | clearInterval(timer);
24 | };
25 | }, [datetime, setDeadLine]);
26 |
27 | return {deadLine};
28 | };
29 |
30 | export default TextTimer;
31 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/TextareaWithLength/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | width: 100%;
6 | border: none;
7 | outline: none;
8 | `;
9 |
10 | const Header = styled.div`
11 | width: 100%;
12 | display: flex;
13 | justify-content: space-between;
14 | margin-bottom: 5px;
15 | `;
16 |
17 | const Title = styled.span`
18 | font-weight: bold;
19 | `;
20 |
21 | const Counter = styled.span`
22 | color: ${props => (props.isOver ? "var(--color-danger)" : "var(--color-gary)")};
23 | font-weight: ${props => (props.isOver ? "700" : "400")};
24 | font-size: 0.9rem;
25 | `;
26 |
27 | const Content = styled.textarea`
28 | font-family: D2Coding, "D2 coding", monosapce;
29 | width: 100%;
30 | height: 20rem;
31 | border: var(--color-primary) solid 1px;
32 | resize: none;
33 | box-sizing: border-box;
34 | border-radius: 10px;
35 | outline: none;
36 | font-size: 1.2rem;
37 | padding: 1rem;
38 | `;
39 |
40 | const Component = ({ title, limit, content, handler, isBlockMode }) => {
41 | const [len, setLen] = useState(content ? content.length : 0);
42 |
43 | const handleContent = event => {
44 | const content = event.target.value;
45 | const validContent =
46 | isBlockMode && content.length > limit ? content.substring(0, limit) : content;
47 | handler(validContent);
48 | setLen(validContent.length);
49 | };
50 |
51 | const handleBlock = e => {
52 | const keyCode = e.keyCode;
53 | const value = e.target.value;
54 | if (keyCode !== 8 && keyCode !== 46 && value.length >= limit) e.preventDefault();
55 | };
56 |
57 | return (
58 |
59 |
60 | {title}
61 | limit}>{`(${len} / ${limit})`}
62 |
63 |
69 |
70 | );
71 | };
72 |
73 | export default Component;
74 |
--------------------------------------------------------------------------------
/client/src/components/Atoms/ToggleButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const Container = styled.div`
5 | position: relative;
6 | width: 60px;
7 | height: 20px;
8 | border: none;
9 | outline: none;
10 | display: flex;
11 | `;
12 |
13 | const Thumb = styled.span`
14 | position: absolute;
15 | width: 20px;
16 | height: 20px;
17 | box-sizing: border-box;
18 | z-index: 1;
19 | right: 0;
20 | border-radius: 50%;
21 | background: ${props => (props.checked ? "var(--color-primary)" : "var(--color-gray)")};
22 | -webkit-box-shadow: 0px 0px 7px 0px var(--color-darkgray-lighter);
23 | -moz-box-shadow: 0px 0px 7px 0px var(--color-darkgray-lighter);
24 | box-shadow: 0px 0px 7px 0px var(--color-darkgray-lighter);
25 |
26 | transition: transform 0.15s ease-in-out;
27 | ${props => (props.checked ? `transform: translateX(-40px)` : "")};
28 | `;
29 |
30 | const Bar = styled.div`
31 | width: 100%;
32 | height: 14px;
33 | background: #dfdfdf;
34 | border-radius: 7px;
35 | margin: auto 5px;
36 | `;
37 |
38 | const Component = ({ checked, onClick }) => {
39 | return (
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default Component;
48 |
--------------------------------------------------------------------------------
/client/src/components/Messenger/Container/ChatContainer/ChatMessage.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import React from "react";
3 | import { dateDiff2Str, sec2date } from "../../../../utils/converter";
4 | const Wrap = styled.div`
5 | width: 19rem;
6 |
7 | display: flex;
8 | flex-direction: ${props => (props.isSend ? "row" : "row-reverse")};
9 | justify-content: ${props => (props.isSend ? "flex-end" : "flex-end")};
10 | padding: 0.25rem 0.25rem;
11 | margin: 0 0.5rem 0 0;
12 | `;
13 |
14 | const MessageText = styled.span`
15 | display: inline-block;
16 | text-align: left;
17 | word-break: break-word;
18 |
19 | font-size: var(--font-size-xs);
20 |
21 | width: 10rem;
22 | padding: 0.3rem 1rem;
23 | border: solid 0.1rem;
24 | border-color: ${props => (props.isSend ? "var(--color-primary-minus0)" : "var(--color-primary)")};
25 | border-radius: 1rem;
26 | `;
27 | const TimeText = styled.div`
28 | font-size: var(--font-size-xxs);
29 | `;
30 |
31 | function ChatMessage(props) {
32 | return (
33 |
34 | {dateDiff2Str(sec2date(props.Time))}
35 | {props.Text}
36 |
37 | );
38 | }
39 |
40 | export default ChatMessage;
41 |
--------------------------------------------------------------------------------
/client/src/components/Messenger/Container/RoomElement/index.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import React, { useState, useEffect } from "react";
3 | import DefaultProfileIcon from "../../../../assets/default-profile.svg";
4 |
5 | import apiConfig from "../../../../config/api";
6 | import pathConfig from "../../../../config/path";
7 |
8 | const { apiUrl } = apiConfig;
9 | const { userid } = pathConfig;
10 |
11 | const Wrap = styled.div`
12 | width: 19.5rem;
13 | height: 3rem;
14 |
15 | display: flex;
16 | flex-direction: row;
17 | padding: 0.25rem 0.25rem;
18 | margin-bottom: 0.5rem;
19 | `;
20 |
21 | const Img = styled.div`
22 | width: 3rem;
23 | height: 3rem;
24 | border-radius: 3rem;
25 | background-color: white;
26 | overflow: hidden;
27 | text-align: center;
28 | margin: 0 0.5rem 0 0;
29 | img {
30 | width: 100%;
31 | height: 100%;
32 | object-fit: contain;
33 | }
34 | `;
35 | const RoomContent = styled.div`
36 | display: flex;
37 | flex-direction: column;
38 | padding: 0.5rem 0;
39 | `;
40 | const HostName = styled.div`
41 | color: var(--color-darkgray-lighter);
42 | font-size: 0.8rem;
43 | text-align: left;
44 | margin-bottom: 0.1rem;
45 | `;
46 | const HostRecentMsg = styled.span`
47 | display: inline-block;
48 | overflow: hidden;
49 | text-overflow: ellipsis;
50 | white-space: nowrap;
51 | width: 15rem;
52 | text-align: left;
53 | `;
54 |
55 | function RoomElement(props) {
56 | const [name, setName] = useState("Anonymous");
57 | const [point, setPoint] = useState(0);
58 | const [profile, setProfile] = useState(null);
59 |
60 | useEffect(() => {
61 | fetch(`${apiUrl}${userid}`, {
62 | method: "POST",
63 | headers: {
64 | "Content-Type": "application/json"
65 | },
66 | body: JSON.stringify({
67 | id: props.roomUserId
68 | })
69 | })
70 | .then(result => {
71 | return result.json();
72 | })
73 | .then(result => {
74 | if (result.name !== "NotFoundError") {
75 | setName(result.name);
76 | setPoint(result.mannerPoint);
77 | setProfile(result.profileUrl);
78 | }
79 | });
80 | }, []);
81 | return (
82 | props.clickroom()}>
83 |
84 |
85 |
86 |
87 |
88 | {name} point:{point}
89 |
90 |
91 | {props.RecentMsg}
92 |
93 |
94 |
95 | );
96 | }
97 |
98 | export default RoomElement;
99 |
--------------------------------------------------------------------------------
/client/src/components/Messenger/CreateButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 | import firebase from "../../../shared/firebase";
4 | import MessengerContext from "../../../context/MessengerContext";
5 |
6 | const Button = styled.button`
7 | margin: 0 var(--margin-xs);
8 | padding: var(--padding-xs);
9 | font-size: 0.5rem;
10 | font-weight: bold;
11 | color: var(--color-secondary);
12 | border-radius: 16px;
13 | display: inline-block;
14 | border: 1px solid var(--color-secondary);
15 | background-color: white;
16 |
17 | &:hover {
18 | color: white;
19 | background-color: var(--color-secondary);
20 | cursor: pointer;
21 | }
22 | `;
23 |
24 | const CreateButton = props => {
25 | const [, setMessengerOpen] = useContext(MessengerContext);
26 |
27 | function makeRoom() {
28 | setMessengerOpen(true);
29 | firebase.makeRoom(props.userId, props.sellerId);
30 | }
31 | return ;
32 | };
33 |
34 | export default CreateButton;
35 |
--------------------------------------------------------------------------------
/client/src/components/Messenger/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect, useContext } from "react";
2 | import Container from "./Container";
3 | import CategoryIcon from "../Organism/CategoryBar/CategoryIcon";
4 | import MessengerContext from "../../context/MessengerContext";
5 |
6 | function Messenger(props) {
7 | const [messengerOpen, setMessengerOpen] = useContext(MessengerContext);
8 | const [show, setShow] = useState(false);
9 | const node = useRef();
10 | useEffect(() => {
11 | if (messengerOpen) {
12 | OpenMessenger();
13 | setMessengerOpen(false);
14 | }
15 | }, [messengerOpen]);
16 | useEffect(() => {
17 | document.addEventListener("mousedown", handleOnBlur);
18 | });
19 | const handleOnBlur = e => {
20 | if (node.current !== (undefined || null)) {
21 | if (!node.current.contains(e.target)) {
22 | setShow(false);
23 | }
24 | }
25 | };
26 | function ChangeState() {
27 | props.onClick();
28 | setShow(!show);
29 | }
30 |
31 | function OpenMessenger() {
32 | setShow(true);
33 | }
34 |
35 | return (
36 |
37 |
38 | ChangeState()}
43 | >
44 |
45 | );
46 | }
47 | export default Messenger;
48 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/AlertDialog/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | position: absolute;
6 | top: 0;
7 | left: 0;
8 | width: 100vw;
9 | height: 100vh;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | z-index: 500;
14 |
15 | background: rgba(64, 64, 64, 0.6);
16 | `
17 |
18 | const Content = styled.div`
19 | width: 30rem;
20 | min-height: 3rem;
21 | background: white;
22 | border-radius: 10px;
23 | overflow: hidden;
24 | padding: 1rem;
25 | `
26 |
27 | const TitleContainer = styled.div`
28 | width: 100%;
29 | height: fit-content;
30 | text-align: left;
31 | `
32 |
33 | const BodyContainer = styled.div`
34 | width: 100%;
35 | min-height: 1rem;
36 | height: fit-content;
37 | margin: 0.3rem 0;
38 | `
39 |
40 | const ButtonContainer = styled.div`
41 | display: flex;
42 | flex-direction: row-reverse;
43 | margin: var(--margin-lg) 0 var(--margin-xs) 0;
44 | `
45 |
46 | const Title = styled.div`
47 | padding: 0.5rem 0;
48 | font-size: 1.3rem;
49 | `
50 |
51 | const Body = styled.div`
52 | margin: 0.3rem 0;
53 | white-space: pre-wrap;
54 | `
55 |
56 | const Button = styled.button`
57 | width: fit-content;
58 | padding: 0.3rem 1rem;
59 | font-size: 1.1rem;
60 | border-radius: 10px;
61 | margin-left: 3px;
62 | color: white;
63 | `
64 |
65 | export const Components = ({ title, content, cancelAble, onCancel, onAccept, onDismiss }) => {
66 | const handleSuccess = async e => {
67 | if (onAccept !== undefined) await onAccept()
68 | if (onDismiss === undefined) return
69 | onDismiss()
70 | }
71 |
72 | const handleCancel = async e => {
73 | if (onCancel !== undefined) await onCancel()
74 | if (onDismiss === undefined) return
75 | onDismiss()
76 | }
77 |
78 | return (
79 |
80 |
81 |
82 | {title}
83 |
84 |
85 | {content}
86 |
87 |
88 |
91 | {cancelAble ? (
92 |
95 | ) : (
96 | undefined
97 | )}
98 |
99 |
100 |
101 | )
102 | }
103 |
104 | export default Components
105 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CardContainer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Card from "../../Atoms/Card";
4 | import NotFoundImg from "../../../assets/notFound.png";
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | padding-left: 7rem;
11 | `;
12 |
13 | const Title = styled.label`
14 | display: flex;
15 | font-size: xx-large;
16 | justify-content: flex-start;
17 | `;
18 |
19 | const CardContainerStyle = styled.div`
20 | display: flex;
21 | justify-content: flex-start;
22 | width: 100%;
23 | height: ${props => (props.isWrap ? "35rem" : "17rem")};
24 | margin-bottom: 2rem;
25 | flex-wrap: ${props => (props.isWrap ? "wrap" : "")};
26 | overflow: ${props => (props.isWrap ? "auto" : "")};
27 | `;
28 |
29 | const NotItemInfo = styled.div`
30 | margin: auto;
31 | text-align: center;
32 | `;
33 |
34 | const CardContainer = ({ title, items, isWrap }) => {
35 | return (
36 |
37 | {title}
38 |
39 | {items.length === 0 ? (
40 |
41 |
42 | 등록된 물품이 없습니다.
43 |
44 | ) : (
45 | items.map(item => )
46 | )}
47 |
48 |
49 | );
50 | };
51 |
52 | export default CardContainer;
53 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/AddButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import FoundImage from "../../../../assets/found.png"
5 | import NotFoundImage from "../../../../assets/notFound.png"
6 | import { size } from "../constant"
7 |
8 | const Container = styled.div`
9 | width: ${size}rem;
10 | height: ${size}rem;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | text-align: center;
15 | flex-direction: column;
16 | background-color: white;
17 | `
18 |
19 | const Button = styled.input`
20 | display: inline-block;
21 | width: 15rem;
22 | height: 15rem;
23 | overflow: hidden;
24 | box-sizing: border-box;
25 | border-radius: 20px;
26 | padding: 15rem 0 0 0;
27 | background: url(${NotFoundImage}) no-repeat center center;
28 | background-size:cover;
29 | border:none;
30 | outline: none;
31 |
32 | transition: background .5s ease-in-out;
33 |
34 | &:hover{
35 | background-image: url('${FoundImage}');
36 | }
37 | `
38 |
39 | const Span = styled.span`
40 | font-family: "BMJUA";
41 | width: 100%;
42 | word-break: keep-all;
43 | `
44 |
45 | const Component = props => {
46 | const { trigger } = props
47 | const handleFile = ev => {
48 | const files = ev.target.files
49 | trigger(files)
50 | }
51 |
52 | return (
53 |
54 |
55 | 이미지를 끌어다 놓거나 클릭해주세요
56 |
57 | )
58 | }
59 |
60 | export default Component
61 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/BeforeButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Left from "@material-ui/icons/ArrowLeft";
4 |
5 | const Container = styled.div`
6 | width: 100%;
7 | display: ${props => (props.show ? "flex" : "none")};
8 | justify-content: center;
9 | align-items: center;
10 | opacity: 0.3;
11 | margin: auto;
12 | z-index: 3;
13 | transition: opacity 0.15s ease-in-out;
14 |
15 | &:hover {
16 | opacity: 1;
17 | }
18 |
19 | svg {
20 | width: 100%;
21 | height: 100%;
22 | }
23 | `;
24 |
25 | const Components = ({ onClick, visible }) => {
26 | return (
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default Components;
34 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/CarouselImage/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import styled from "styled-components"
3 |
4 | import RemoveButton from "../RemoveButton"
5 |
6 | const Container = styled.div`
7 | width: inherit;
8 | height: inherit;
9 | position: relative;
10 | `
11 |
12 | const Image = styled.img`
13 | width: 100%;
14 | height: 100%;
15 | object-fit: cover;
16 | margin: auto;
17 | `
18 |
19 | const CloseDiv = styled.div`
20 | position: absolute;
21 | top: 0.5em;
22 | right: 0.5em;
23 | display: ${props => (props.show ? "block" : "none")};
24 | `
25 |
26 | const Components = ({ src, readOnly, onRemove }) => {
27 | const [hover, setHover] = useState(false)
28 |
29 | const handleMouseOn = event => setHover(true)
30 | const handleMouseOut = event => setHover(false)
31 |
32 | return (
33 |
34 |
35 | {readOnly ? (
36 | undefined
37 | ) : (
38 |
39 | {
41 | onRemove()
42 | }}
43 | />
44 |
45 | )}
46 |
47 | )
48 | }
49 |
50 | export default Components
51 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/NextButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Right from "@material-ui/icons/ArrowRight";
4 |
5 | const Container = styled.div`
6 | width: 100%;
7 | display: ${props => (props.show ? "flex" : "none")};
8 | justify-content: center;
9 | align-items: center;
10 | opacity: 0.3;
11 | margin: auto;
12 | z-index: 3;
13 | transition: opacity 0.15s ease-in-out;
14 |
15 | &:hover {
16 | opacity: 1;
17 | }
18 |
19 | svg {
20 | width: 100%;
21 | height: 100%;
22 | }
23 | `;
24 |
25 | const Components = ({ onClick, visible }) => {
26 | return (
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default Components;
34 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/RemoveButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 |
4 | const Division = styled.div`
5 | position:relative;
6 | width: 15px;
7 | height: 15px;
8 | border-radius:50%;
9 | background: white;
10 |
11 | &::before, &::after {
12 | position: absolute;
13 | top: 7px;
14 | left: 2px;
15 | width: 10px;
16 | height: 1.5px;
17 | content: "";
18 | background-color: black;
19 | display: block;
20 | }
21 |
22 | &::before{
23 | -ms-transform: rotate(-45deg);
24 | -webkit-transform: rotate(-45deg);
25 | transform: rotate(-45deg);
26 | }
27 |
28 | &::after{
29 | -ms-transform: rotate(45deg);
30 | -webkit-transform: rotate(45deg);
31 | transform: rotate(45deg);
32 | }
33 |
34 | &:hover{
35 | cursor: pointer;
36 | }
37 | `
38 | const Components = ({onClick}) => {
39 | return (
40 |
41 | )
42 | }
43 |
44 | export default Components;
--------------------------------------------------------------------------------
/client/src/components/Molecules/Carousel/constant.jsx:
--------------------------------------------------------------------------------
1 | export const limits = 10
2 | export const size = 22
3 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CustomModal/FailModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import FailIcon from "../../../assets/fail.svg";
3 | import styled from "styled-components";
4 | import ModalContext from "../../../context/ModalContext";
5 |
6 | const FailModalStyle = styled.div`
7 | width: 400px;
8 | height: 400px;
9 | display: flex;
10 | flex-direction: column;
11 | background-color: white;
12 | border-radius: 8px;
13 | `;
14 |
15 | const ModalHeader = styled.div`
16 | flex: 1;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | `;
21 |
22 | const ModalBody = styled.div`
23 | height: 50px;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | `;
28 |
29 | const ModalFooter = styled.div`
30 | height: 120px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | `;
35 |
36 | const FailImage = styled.img`
37 | width: 120px;
38 | height: 120px;
39 | `;
40 |
41 | const FailText = styled.div`
42 | font-size: var(--font-size-lg);
43 | font-weight: bold;
44 | color: var(--color-darkgray);
45 | `;
46 |
47 | const CheckButton = styled.button`
48 | width: 120px;
49 | padding: 8px;
50 | font-size: var(--font-size-lg);
51 | border: 2px solid var(--color-primary);
52 | color: var(--color-primary);
53 | font-weight: bold;
54 | border-radius: 8px;
55 | background-color: white;
56 |
57 | &:hover {
58 | background-color: var(--color-primary);
59 | color: white;
60 | }
61 | `;
62 |
63 | const FailModal = ({ message }) => {
64 | const [, setModal] = useContext(ModalContext);
65 | const handleClickCheckBtn = e => {
66 | setModal(state => ({ ...state, isOpen: false }));
67 | };
68 |
69 | return (
70 |
71 |
72 |
73 |
74 |
75 | {message}
76 |
77 |
78 | 확인
79 |
80 |
81 | );
82 | };
83 |
84 | export default FailModal;
85 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CustomModal/Modal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from "react";
2 | import styled from "styled-components";
3 | import ModalContext from "../../../context/ModalContext";
4 |
5 | const ModalStyle = styled.div`
6 | position: fixed;
7 | width: 100%;
8 | height: 100%;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | z-index: 2000;
13 | `;
14 |
15 | const ModalBackGround = styled.div`
16 | position: absolute;
17 | width: 100%;
18 | height: 100%;
19 | background-color: black;
20 | opacity: 0.3;
21 | `;
22 |
23 | const ModalContentWrapper = styled.div`
24 | position: relative;
25 | `;
26 |
27 | export const Modal = () => {
28 | const [modal, setModal] = useContext(ModalContext);
29 |
30 | const ModalContent = modal.component;
31 |
32 | const handleClickModalBack = e => {
33 | setModal(state => ({ ...state, isOpen: false }));
34 | };
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CustomModal/SuccessModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import SuccessIcon from "../../../assets/success.svg";
3 | import styled from "styled-components";
4 | import ModalContext from "../../../context/ModalContext";
5 |
6 | const SuccessModalStyle = styled.div`
7 | width: 400px;
8 | height: 400px;
9 | display: flex;
10 | flex-direction: column;
11 | background-color: white;
12 | border-radius: 8px;
13 | `;
14 |
15 | const ModalHeader = styled.div`
16 | flex: 1;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | `;
21 |
22 | const ModalBody = styled.div`
23 | height: 50px;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | `;
28 |
29 | const ModalFooter = styled.div`
30 | height: 120px;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | `;
35 |
36 | const SuccessImage = styled.img`
37 | width: 120px;
38 | height: 120px;
39 | `;
40 |
41 | const SuccessText = styled.div`
42 | font-size: var(--font-size-lg);
43 | font-weight: bold;
44 | color: var(--color-darkgray);
45 | `;
46 |
47 | const CheckButton = styled.button`
48 | width: 120px;
49 | padding: 8px;
50 | font-size: var(--font-size-lg);
51 | border: 2px solid var(--color-success);
52 | color: var(--color-success);
53 | font-weight: bold;
54 | border-radius: 8px;
55 | background-color: white;
56 |
57 | &:hover {
58 | background-color: var(--color-success);
59 | color: white;
60 | }
61 | `;
62 |
63 | const SuccessModal = ({ message }) => {
64 | const [, setModal] = useContext(ModalContext);
65 | const handleClickCheckBtn = e => {
66 | setModal(state => ({ ...state, isOpen: false }));
67 | };
68 |
69 | return (
70 |
71 |
72 |
73 |
74 |
75 | {message}
76 |
77 |
78 | 확인
79 |
80 |
81 | );
82 | };
83 |
84 | export default SuccessModal;
85 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CustomModal/UserModalCommonStyles.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const ModalBody = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | background: white;
7 | border-radius: 5px;
8 | `;
9 |
10 | const Input = styled.input`
11 | padding: 1em;
12 | border: 0.1px solid;
13 | font-family: "BMJUA";
14 | `;
15 |
16 | const SubmitButton = styled.button`
17 | padding: 1em;
18 | color: white;
19 | background: var(--color-quaternary);
20 | font-family: "BMJUA";
21 | border-radius: 3px;
22 | `;
23 |
24 | const ButtonContainer = styled.div`
25 | display: flex;
26 | justify-content: flex-end;
27 | margin-top: 0.2em;
28 | `;
29 |
30 | export { ModalBody, Input, SubmitButton, ButtonContainer };
31 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/CustomModal/constant.jsx:
--------------------------------------------------------------------------------
1 | export const validate = {
2 | id: /^[a-z0-9-_]{5,20}$/,
3 | password: {
4 | length: /.{8,16}/,
5 | alphabet: /^(?=.*[a-zA-Z])/,
6 | number: /^(?=.*\d)/,
7 | special: /^(?=.*\W)/
8 | },
9 | email: /^[a-z0-9_+.-]+@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/
10 | };
11 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/InfiniteScroll/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import useIntersect from "./useIntersect";
4 | import Loading from "../../Atoms/LoadingBar";
5 | import NotFoundImage from "../../../assets/notFound.png";
6 |
7 | const Container = styled.div`
8 | position: relative;
9 | width: 100%;
10 | min-height: 100%;
11 | display: flex;
12 | flex-direction: column;
13 | `;
14 |
15 | const LoadingContainer = styled.div`
16 | position: absolute;
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | z-index: 2;
21 | width: 100%;
22 | height: 100%;
23 | opacity: 0.7;
24 | background-color: white;
25 | `;
26 |
27 | const NotFoundDiv = styled.div`
28 | width: 100%;
29 | height: 100%;
30 | display: flex;
31 | flex-direction: column;
32 | justify-content: center;
33 | align-items: center;
34 | `;
35 |
36 | const NotFoundSpan = styled.span`
37 | font-weight: "BMJUA";
38 | font-size: 1rem;
39 | font-weight: bold;
40 | `;
41 |
42 | const renderNotFound = () => {
43 | return (
44 |
45 |
46 | 검색 기록이 없습니다 ㅠㅠ
47 |
48 | );
49 | };
50 |
51 | const Component = ({ fetcher, drawer, refresh, hasMore }) => {
52 | const [loading, setLoading] = useState(true);
53 | const [list, setList] = useState([]);
54 |
55 | const [, setRef] = useIntersect(async (entry, observer) => {
56 | setLoading(true);
57 | observer.unobserve(entry.target);
58 | await update();
59 | observer.observe(entry.target);
60 | setLoading(false);
61 | }, {});
62 |
63 | useEffect(() => {
64 | if (refresh) {
65 | setList([]);
66 | setLoading(true);
67 | }
68 | }, [refresh]);
69 |
70 | const update = async () => {
71 | const data = await fetcher();
72 | const components = drawer(data);
73 |
74 | setList(prev => [...prev, ...components]);
75 |
76 | return components.length;
77 | };
78 |
79 | return (
80 |
81 | {loading ? (
82 |
83 |
84 |
85 | ) : (
86 | undefined
87 | )}
88 | {list.length ? list.map(v => v) : renderNotFound()}
89 | {hasMore ? : undefined}
90 |
91 | );
92 | };
93 |
94 | export default Component;
95 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/InfiniteScroll/useIntersect.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from "react";
2 |
3 | const baseOption = {
4 | root: null,
5 | threshold: 0.5,
6 | rootMargin: "0px"
7 | };
8 | const useIntersect = (onIntersect, option) => {
9 | const [ref, setRef] = useState(null);
10 | const checkIntersect = useCallback(([entry], observer) => {
11 | if (entry.isIntersecting) {
12 | onIntersect(entry, observer);
13 | }
14 | }, []);
15 | useEffect(() => {
16 | let observer;
17 | if (ref) {
18 | observer = new IntersectionObserver(checkIntersect, {
19 | ...baseOption,
20 | ...option
21 | });
22 | observer.observe(ref);
23 | }
24 | return () => observer && observer.disconnect();
25 | }, [ref, option.root, option.threshold, option.rootMargin, checkIntersect]);
26 | return [ref, setRef];
27 | };
28 |
29 | export default useIntersect;
30 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/MoneyBox/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | width: 100%;
6 | display: flex;
7 |
8 | border-radius: 5px;
9 | border: #dfdfdf solid 1.5px;
10 | overflow: hidden;
11 |
12 | input {
13 | text-align: right;
14 | }
15 |
16 | &:focus-within {
17 | border: var(--color-primary) solid 1.5px;
18 |
19 | div {
20 | background: var(--color-primary);
21 | color: white;
22 | }
23 | }
24 | `
25 |
26 | const WonDiv = styled.div`
27 | width: 2rem;
28 | height: 2rem;
29 | background: #dfdfdf;
30 | color: black;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | `
35 | const NonBorderBox = styled.input`
36 | font-family: "BMJUA";
37 | width: 100%;
38 | padding: 0.25rem 0.5rem;
39 | box-sizing: border-box;
40 | border: none;
41 | outline: none;
42 | font-size: var(--font-size-md);
43 | `
44 |
45 | const Component = ({ money, handler, disabled, ...otherProps }) => {
46 | const changeHandler = ev => {
47 | const input = ev.target.value.replace(/[^0-9]/g, "")
48 | handler(input.substring(0, 9))
49 | }
50 |
51 | return (
52 |
53 |
58 |
59 | 원
60 |
61 |
62 | )
63 | }
64 |
65 | export default Component
66 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/NotifyList/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext } from "react";
2 | import styled from "styled-components";
3 | import { NotifyItem } from "./NotifyItem";
4 | import NotificationContext from "../../../context/NotificationContext";
5 |
6 | const BackColor = styled.div`
7 | background-color: var(--color-gray-lighter);
8 | overflow-y: auto;
9 | overflow-x: hidden;
10 | width: 100%;
11 | height: 100%;
12 | &::-webkit-scrollbar {
13 | display: none !important;
14 | }
15 | `;
16 |
17 | const InfoDiv = styled.div`
18 | display: flex;
19 | flex-direction: column;
20 | background-color: var(--color-gray-lighter);
21 | align-items: center;
22 | width: 100%;
23 | `;
24 |
25 | function Component() {
26 | const [notifications] = useContext(NotificationContext);
27 |
28 | return (
29 |
30 |
31 | {notifications.map(noti => {
32 | return (
33 |
37 | );
38 | })}
39 |
40 |
41 | );
42 | }
43 |
44 | export default Component;
45 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/RatingDialog/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import AlertDialog from "../AlertDialog";
4 | import apiConfig from "../../../config/api";
5 | import pathConfig from "../../../config/path";
6 |
7 | const { apiUrl } = apiConfig;
8 | const { productsRating } = pathConfig;
9 | const ReportContainer = styled.div`
10 | display: flex;
11 | flex-direction: column;
12 | justify-content: center;
13 | `;
14 |
15 | function Components(props) {
16 | function ReportSubmit(e) {
17 | e.preventDefault();
18 | if (e.target.rate.value === "") {
19 | return alert("점수를 선택해 주세요.");
20 | } else {
21 | fetch(`${apiUrl}${productsRating}`, {
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json"
25 | },
26 | body: JSON.stringify({
27 | targetUserId: props.targetId,
28 | productId: props.productId,
29 | point: e.target.rate.value,
30 | isSeller: props.isSeller
31 | })
32 | }).then(result => {
33 | if (result) {
34 | alert("의견 감사합니다.");
35 | props.doCheck();
36 | props.onClick();
37 | } else {
38 | alert("잘못된 요청입니다.");
39 | props.onClick();
40 | }
41 | });
42 | }
43 | }
44 |
45 | let ReportContent = (
46 |
47 |
48 |
57 |
58 |
59 | );
60 | return (
61 | <>
62 |
70 | >
71 | );
72 | }
73 |
74 | export default Components;
75 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/SelectBox/List/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import ListItem from "../ListItem";
5 |
6 | const Container = styled.div`
7 | width: 100%;
8 | height: ${props => (props.open ? 2 : 0) * props.count}rem;
9 | box-sizing: border-box;
10 | overflow: hidden;
11 | transition: height 0.2s ease-in-out;
12 | border-radius: inherit;
13 |
14 | ${props => {
15 | if (props.open) return `border: #aaaaaa solid 1px;`;
16 | }}
17 | `;
18 |
19 | const Wrapper = styled.div`
20 | width: 100%;
21 | height: ${props => props.count * 2}rem;
22 | overflow-y: auto;
23 | overflow-x: hidden;
24 | `;
25 |
26 | const Component = props => {
27 | const { open, show, selected, list, handler } = props;
28 |
29 | return (
30 |
31 |
32 | {list.map((value, idx) => (
33 | {
38 | handler(idx);
39 | }}
40 | />
41 | ))}
42 |
43 |
44 | );
45 | };
46 |
47 | export default Component;
48 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/SelectBox/ListItem/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | width: 100%;
6 | height: 2em;
7 | display: flex;
8 | align-content: center;
9 | color: ${props => (props.active ? "var(--color-primary)" : "black")};
10 | background: white;
11 | border-radius: 5px;
12 | padding: 0 0.25rem;
13 | font-family: "BMJUA";
14 | font-weight: 600;
15 |
16 | &:hover {
17 | background: #dfdfdf;
18 | }
19 | `
20 |
21 | const Text = styled.span`
22 | width: fit-content;
23 | height: fit-content;
24 | margin: auto 0;
25 | `
26 |
27 | const Component = props => {
28 | const { selected, onClick } = props
29 |
30 | return (
31 |
32 | {props.text}
33 |
34 | )
35 | }
36 |
37 | export default Component
38 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/SelectBox/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import styled from "styled-components";
3 | import ItemList from "./List";
4 |
5 | const Container = styled.div`
6 | position: relative;
7 | width: 100%;
8 | max-width: 10rem;
9 | height: 2rem;
10 | `;
11 |
12 | const ItemListDiv = styled.div`
13 | position: absolute;
14 | width: 100%;
15 | top: 2rem;
16 | z-index: 30;
17 | background: white;
18 | border-radius: 5px;
19 | `;
20 |
21 | const BoxHeader = styled.button`
22 | width: 100%;
23 | height: 100%;
24 | padding: 0.25rem 0.5rem;
25 | outline: none;
26 | background: white;
27 | border-radius: 5px;
28 | border: #aaaaaa solid 1px;
29 | text-align: left;
30 | font-weight: 700;
31 |
32 | &:hover {
33 | background: #dfdfdf;
34 | }
35 | `;
36 |
37 | const Component = ({ list, selected, show, handler }) => {
38 | const [header, setHeader] = useState("선택해주세요");
39 | const [open, setOpen] = useState(false);
40 | const handleListOpen = event => setOpen(!open);
41 |
42 | const listEvent = idx => {
43 | setHeader(list[idx]);
44 | handler(idx);
45 | setOpen(false);
46 | };
47 |
48 | return (
49 |
50 | {header}
51 |
52 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default Component;
65 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/SmallCardContainer/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import SmallCard from "../../Atoms/SmallCard";
4 | import NotFoundImg from "../../../assets/notFound.png";
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | padding-left: 2rem;
11 | padding-right: 2rem;
12 | width: 100%;
13 | `;
14 |
15 | const Title = styled.label`
16 | display: flex;
17 | font-size: x-large;
18 | justify-content: flex-start;
19 | `;
20 |
21 | const SmallCardContainerStyle = styled.div`
22 | display: flex;
23 | justify-content: flex-start;
24 | width: 100%;
25 | height: ${props => (props.isWrap ? "35rem" : "17rem")};
26 | margin-bottom: 2rem;
27 | flex-wrap: ${props => (props.isWrap ? "wrap" : "")};
28 | overflow: ${props => (props.isWrap ? "auto" : "")};
29 | `;
30 |
31 | const NotItemInfo = styled.div`
32 | margin: auto;
33 | text-align: center;
34 | `;
35 |
36 | const SmallCardContainer = ({ title, items, isWrap }) => {
37 | return (
38 |
39 | {title}
40 |
41 | {items.length === 0 ? (
42 |
43 |
44 | 등록된 물품이 없습니다.
45 |
46 | ) : (
47 | items.map(item => )
48 | )}
49 |
50 |
51 | );
52 | };
53 |
54 | export default SmallCardContainer;
55 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/TitleListBox/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Container = styled.div`
5 | font-family: "BMDOHYEON";
6 | width: 20em;
7 | border: var(--color-primary) solid 1px;
8 | box-sizing: border-box;
9 | `
10 |
11 | const ListContainer = styled.div`
12 | width: 100%;
13 | height: 20em;
14 | overflow-x: hidden;
15 | overflow-y: auto;
16 | box-sizing: border-box;
17 | margin-top: 0.2em;
18 | background-color: white;
19 | `
20 |
21 | const Components = props => {
22 | const { title, list } = props
23 |
24 | return (
25 |
26 | {title}
27 | {list !== undefined ? list.map(value => value) : undefined}
28 |
29 | )
30 | }
31 |
32 | export default Components
33 |
--------------------------------------------------------------------------------
/client/src/components/Molecules/TradeBase/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import { dateDiff2Str } from "../../../utils/converter"
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | width: 100%;
9 | min-height: fit-content;
10 | margin-bottom: 5px;
11 | justify-content: space-between;
12 | `
13 |
14 | const Thumbnail = styled.img`
15 | width: 6rem;
16 | height: 6rem;
17 |
18 | transition: width 0.15s ease-in-out, height 0.15s ease-in-out;
19 | `
20 |
21 | const Title = styled.span`
22 | display: flex;
23 | width: 20rem;
24 | height: fit-content;
25 | font-size: 1.3rem;
26 | margin: auto 1rem;
27 | `
28 |
29 | const Status = styled.span`
30 | min-width: 7rem;
31 | height: fit-content;
32 | font-size: 1.1rem;
33 | margin: auto 0.5rem;
34 | text-align: center;
35 | font-weight: 700;
36 | `
37 |
38 | const Price = styled.span`
39 | min-width: 10rem;
40 | height: fit-content;
41 | font-size: 1rem;
42 | margin: auto 0.5rem;
43 | text-align: end;
44 | `
45 |
46 | const Date = styled.span`
47 | min-width: 5rem;
48 | height: fit-content;
49 | margin: auto 0.5rem;
50 | text-align: end;
51 | font-weight: 700;
52 | color: var(--color-gray-lighter-plus);
53 | `
54 |
55 | const Component = ({ title, thumbnail, status, price, time }) => {
56 | return (
57 |
58 |
59 | {title}
60 | {status}
61 | {price}원
62 | {dateDiff2Str(time)}
63 |
64 | )
65 | }
66 |
67 | export default Component
68 |
--------------------------------------------------------------------------------
/client/src/components/Organism/AuctionGraph/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 | import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from "recharts";
4 | import ProductPageContext from "../../../context/ProductPageContext";
5 |
6 | const AuctionGraphStyle = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | border: 1px solid var(--color-gray);
12 | border-radius: 8px;
13 | font-size: 0.9rem;
14 | padding: var(--padding-sm);
15 | `;
16 |
17 | const AuctionGraphTitle = styled.div`
18 | width: 100%;
19 | padding-left: var(--padding-md);
20 | margin-bottom: var(--margin-xl);
21 | font-size: 1.1rem;
22 | font-weight: bold;
23 | color: var(--color-darkgray);
24 | `;
25 |
26 | const AuctionCountText = styled.span`
27 | margin-left: 16px;
28 | font-size: 0.9rem;
29 | color: var(--color-primary);
30 | `;
31 |
32 | const AuctionGraph = () => {
33 | const [productPageState] = useContext(ProductPageContext);
34 |
35 | const { bids } = productPageState;
36 |
37 | return (
38 |
39 |
40 | 경매 현황총 {bids.length}건
41 |
42 |
48 |
49 |
50 |
51 | {bids.length > 0 ? (
52 |
53 | ) : (
54 | undefined
55 | )}
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default AuctionGraph;
63 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/CategoryIcon/ImageIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const ImgIconStyle = styled.div`
5 | position: absolute;
6 | display: flex;
7 |
8 | top: 0;
9 | left: 0;
10 | bottom: 0;
11 | right: 0;
12 | z-index: 0;
13 | justify-content: center;
14 | align-items: center;
15 | background: ${props => props.color};
16 | img {
17 | width: 70%;
18 | height: 70%;
19 | }
20 | `;
21 |
22 | const ImgIcon = ({ color, img }) => {
23 | return (
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default ImgIcon;
31 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/CategoryIcon/TextIcon.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const TextIcon = styled.span`
4 | font-family: "BMJUA";
5 | font-size: 1em;
6 | position: absolute;
7 | display: grid;
8 | color: var(--color-secondary-lighter);
9 | background: white;
10 |
11 | cursor: pointer;
12 |
13 | top: 0;
14 | left: 0;
15 | bottom: 0;
16 | right: 0;
17 | z-index: 0;
18 | opacity: 0;
19 | transition: opacity 0.15s ease-in-out, color 0.15s ease-in-out;
20 |
21 | text-align: center;
22 | align-items: center;
23 |
24 | &:hover {
25 | opacity: 1;
26 | color: ${props => props.color};
27 | }
28 | `;
29 |
30 | export default TextIcon;
31 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/CategoryIcon/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import ImgIcon from "./ImageIcon";
4 | import TextIcon from "./TextIcon";
5 | import NotiNumber from "../../../Atoms/NotiNumber";
6 |
7 | const Wrapper = styled.li`
8 | display: flex;
9 | position: relative;
10 | width: 100%;
11 | padding-top: 100%;
12 | margin-bottom: 0.5em;
13 | border-radius: 20%;
14 | overflow: hidden;
15 | `;
16 |
17 | const Components = props => {
18 | const { color, img, active, onClick, text, idx } = props;
19 |
20 | return (
21 |
22 | {text === "알림" ? : null}
23 |
24 |
25 | {text}
26 |
27 |
28 | );
29 | };
30 |
31 | export default Components;
32 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/ExpandList/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { Link } from "react-router-dom";
4 | import UserInfoBox from "../../../Molecules/UserInfoBox";
5 | import NotifyList from "../../../Molecules/NotifyList";
6 |
7 | const Container = styled.div`
8 | display: flex;
9 | flex-direction: column;
10 | width: ${props => (props.idx === 999 ? "21" : "15")}rem;
11 | height: 100%;
12 | overflow-y: auto;
13 | background-color: var(--color-secondary-minus1);
14 | box-sizing: border-box;
15 | z-index: 1;
16 | `;
17 |
18 | const DetailCategory = styled.div`
19 | font-family: "BMJUA";
20 | font-size: large;
21 | text-align: center;
22 | padding: 1em;
23 | cursor: pointer;
24 |
25 | transition: all 0.3s ease-in-out;
26 | &:hover {
27 | background-color: var(--color-secondary-plus0);
28 | }
29 |
30 | label {
31 | cursor: pointer;
32 | }
33 | `;
34 | const StyledLink = styled(Link)`
35 | text-decoration: none;
36 | color: black;
37 | `;
38 |
39 | const Components = ({ idx, open, details, onClick }) => {
40 | return (
41 |
42 | {Number(idx) === 0 ? (
43 |
44 | ) : Number(idx) === 999 ? (
45 |
46 | ) : (
47 | details.map((category, index) => (
48 |
49 |
50 |
51 |
52 |
53 | ))
54 | )}
55 |
56 | );
57 | };
58 |
59 | export default Components;
60 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/LoginButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const LoginButtonStyle = styled.button`
5 | font-family: "BMJUA";
6 | width: 100%;
7 | height: 3.5em;
8 | border-radius: 20%;
9 | background: var(--color-quaternary);
10 | color: white;
11 |
12 | transition: all 0.2s ease-in-out;
13 |
14 | &:hover {
15 | color: var(--color-quaternary);
16 | background: white;
17 | }
18 | `;
19 |
20 | const LoginButton = ({ onClick }) => {
21 | return 로그인;
22 | };
23 |
24 | export default LoginButton;
25 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/Logo/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { Link } from "react-router-dom"
3 | import Logo from "../../../../assets/logo.svg"
4 | import styled from "styled-components"
5 |
6 | const Image = styled.img`
7 | width: 80%;
8 | height: 80%;
9 | object-fit: contain;
10 | `
11 |
12 | const LogoContainer = styled.div`
13 | display: flex;
14 | width: 5em;
15 | height: 5em;
16 | justify-content: center;
17 | align-items: center;
18 | `
19 |
20 | const Components = () => {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default Components
31 |
--------------------------------------------------------------------------------
/client/src/components/Organism/CategoryBar/Profile/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 | import DefaultProfileIcon from "../../../../assets/default-profile.svg";
4 | import userContext from "../../../../context/UserContext";
5 |
6 | const ProfileContainer = styled.div`
7 | display: flex;
8 | width: 100%;
9 | height: 3em;
10 | overflow: hidden;
11 | background: white;
12 | border-radius: 50%;
13 | justify-content: center;
14 | align-items: center;
15 | cursor: pointer;
16 | img {
17 | width: 100%;
18 | height: 100%;
19 | object-fit: contain;
20 | }
21 | `;
22 |
23 | const Profile = ({ onClick }) => {
24 | const [user] = useContext(userContext);
25 | return (
26 |
27 |
33 |
34 | );
35 | };
36 |
37 | export default Profile;
38 |
--------------------------------------------------------------------------------
/client/src/components/Organism/Chat/Chat.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const ChatStyle = styled.div`
5 | display: flex;
6 | padding: var(--padding-md) var(--padding-sm);
7 | `;
8 |
9 | const AvatarBox = styled.div``;
10 |
11 | const Avatar = styled.img`
12 | width: 40px;
13 | height: 40px;
14 | border-radius: 50%;
15 | `;
16 |
17 | const Content = styled.div`
18 | padding-left: var(--padding-md);
19 | `;
20 |
21 | const IdText = styled.div`
22 | font-weight: bold;
23 | font-size: 0.9rem;
24 | color: var(--color-darkgray);
25 | `;
26 | const SellerIdText = styled.div`
27 | font-weight: bold;
28 | font-size: 0.9rem;
29 | color: var(--color-darkgray);
30 | `;
31 | const MessageText = styled.div`
32 | font-size: 0.8rem;
33 | word-break: break-word;
34 | `;
35 |
36 | const Chat = ({ chat, isSeller }) => {
37 | return (
38 |
39 |
40 |
41 |
42 |
43 | {isSeller ? {chat.id}(판매자) : {chat.id}}
44 | {chat.text}
45 |
46 |
47 | );
48 | };
49 |
50 | export default Chat;
51 |
--------------------------------------------------------------------------------
/client/src/components/Organism/Chat/ChatSend.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const ChatSendStyle = styled.form`
5 | display: flex;
6 | justify-content: center;
7 | width: 100%;
8 | `;
9 |
10 | const ChatInput = styled.input`
11 | flex: 1;
12 | border: 1px solid var(--color-gray);
13 | border-radius: 8px;
14 | padding: var(--padding-sm);
15 | font-size: 0.8rem;
16 | `;
17 |
18 | const SendButton = styled.button`
19 | width: 48px;
20 | border: 1px solid var(--color-darkgray);
21 | background-color: var(--color-darkgray);
22 | color: white;
23 | font-weight: bold;
24 | border-radius: 8px;
25 | margin-left: 8px;
26 | `;
27 |
28 | const ChatSend = ({ message, onSubmit, onChange }) => {
29 | return (
30 |
31 |
36 | 전송
37 |
38 | );
39 | };
40 |
41 | export default ChatSend;
42 |
--------------------------------------------------------------------------------
/client/src/components/Organism/ItemCategorySelector/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import ListBox from '../../Molecules/TitleListBox'
4 | import ListItem from '../../Atoms/BtnListItem'
5 | import ListHeader from '../../Atoms/BoxHeader'
6 |
7 | const Container = styled.div`
8 | width:100%;
9 | display:flex;
10 | justify-content:space-between;
11 | `
12 |
13 | const generateTitle = (text) => {
14 | return
15 | }
16 |
17 | const generateCompoList = (list, selected, handler) => {
18 | if (list === undefined) return;
19 |
20 | return list.map((value, idx) => handler(idx)} /> )
21 | }
22 |
23 | const Components = (props) => {
24 |
25 | const { lTitle, lList, rTitle, rList, lIdx, lHandler, rIdx, rHandler } = props
26 |
27 | const leftEvent = idx => { lHandler(idx); rHandler(-1) }
28 | const rightEvent = idx => { rHandler(idx) }
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 |
36 | >
37 | )
38 | }
39 |
40 | export default Components
--------------------------------------------------------------------------------
/client/src/components/Organism/RegisterProgress/Button/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | const Progress = styled.div`
5 | width: 15em;
6 | height: 3em;
7 | border: var(--color-primary) 1.5px solid;
8 | border-radius: 3em;
9 | background: ${props =>
10 | props.disabled ? "var(--color-gray-lighter)" : props.active ? "var(--color-primary)" : "white"};
11 | color: ${props => (props.active ? "white" : "var(--color-primary)")};
12 | font-weight: bold;
13 | display: flex;
14 |
15 | cursor: pointer;
16 | `
17 |
18 | const Content = styled.span`
19 | margin: auto;
20 | `
21 |
22 | const Components = props => {
23 | const { disabled, active, text, onClick } = props
24 |
25 | return (
26 |
29 | )
30 | }
31 |
32 | export default Components
33 |
--------------------------------------------------------------------------------
/client/src/components/Organism/RegisterProgress/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import ProgressButton from "./Button";
5 |
6 | const Container = styled.div`
7 | width: 100%;
8 | height: 7em;
9 | display: flex;
10 | flex-direction: column;
11 | position: relative;
12 | justify-content: center;
13 | align-items: center;
14 |
15 | margin: auto 0;
16 | `;
17 |
18 | const ButtonDiv = styled.div`
19 | display: flex;
20 | width: 100%;
21 | height: fit-content;
22 | border: none;
23 | outline: none;
24 | justify-content: space-between;
25 | z-index: 1;
26 | `;
27 |
28 | const CenterLine = styled.div`
29 | width: 100%;
30 | height: 1.5px;
31 | border: var(--color-primary) dashed 0.75px;
32 | position: absolute;
33 | line-height: 50%;
34 | box-sizing: border-box;
35 | `;
36 |
37 | const renderProgress = (show, component) => {
38 | if (!show) return;
39 | return component;
40 | };
41 |
42 | const Components = props => {
43 | const { phase, maxPhase, list, event } = props;
44 | let show = phase !== list.length - 1;
45 |
46 | return (
47 |
48 | {renderProgress(
49 | show,
50 | <>
51 |
52 | {list.map((value, idx) => {
53 | return (
54 | maxPhase}
57 | text={value}
58 | active={phase === idx}
59 | onClick={ev => {
60 | if (idx <= maxPhase) event(idx);
61 | else alert("작성되지 않은 정보가 있습니다.");
62 | }}
63 | />
64 | );
65 | })}
66 |
67 |
68 | >
69 | )}
70 |
71 | );
72 | };
73 |
74 | export default Components;
75 |
--------------------------------------------------------------------------------
/client/src/components/Organism/RegisterTermSelector/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 |
4 | import SelectBox from "../../Molecules/SelectBox"
5 |
6 | const Container = styled.div`
7 | width: 100%;
8 | flex-direction: column;
9 | justify-content: flex-end;
10 | text-align: right;
11 | `
12 |
13 | const ItemDiv = styled.div`
14 | display: flex;
15 | width: 100%;
16 | justify-content: space-between;
17 | `
18 |
19 | const TermDescription = styled.span`
20 | color: var(--color-darkgray-lighter);
21 | font-size: 0.85rem;
22 | `
23 |
24 | const Title = styled.span`
25 | align-self: flex-start;
26 | font-size: 1.1rem;
27 | margin: auto 0;
28 | `
29 |
30 | const Component = props => {
31 | const { title, data, selected, handler } = props
32 |
33 | const [list, desc] = data
34 |
35 | return (
36 |
37 |
38 | {title}
39 |
40 |
41 | {desc ? desc[selected] : ""}
42 |
43 | )
44 | }
45 |
46 | export default Component
47 |
--------------------------------------------------------------------------------
/client/src/components/Organism/TradeBox/constants.jsx:
--------------------------------------------------------------------------------
1 | export const remove = "등록 취소"
2 | export const update = "상품 수정"
3 |
--------------------------------------------------------------------------------
/client/src/components/Organism/TradeBox/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import styled from "styled-components"
3 |
4 | import TradeBase from "../../Molecules/TradeBase"
5 | import { remove, update } from "./constants"
6 |
7 | const Container = styled.div`
8 | width: 100%;
9 | height: fit-content;
10 | border-bottom: var(--color-gray) solid 1px;
11 | margin-bottom: var(--margin-xs);
12 | `
13 |
14 | const LinkWrapper = styled.a`
15 | width: fit-content;
16 | height: fit-content;
17 | text-decoration: none;
18 | color: black;
19 | `
20 |
21 | const ButtonDiv = styled.div`
22 | display: flex;
23 | flex-direction: row-reverse;
24 | width: 100%;
25 | height: ${props => (props.hover ? 3 : 0)}rem;
26 | overflow: hidden;
27 | box-sizing: border-box;
28 | align-items: center;
29 |
30 | transition: height 0.15s ease-in-out;
31 | `
32 |
33 | const Button = styled.button`
34 | background: none;
35 | height: fit-content;
36 | margin: var(--margin-xs);
37 | padding: var(--padding-xs) var(--padding-sm);
38 | font-size: var(--font-size-lg);
39 | font-weight: 700;
40 | color: white;
41 | border-radius: 10px;
42 | `
43 |
44 | const Components = ({ id, link, onDelete, onUpdate, ...otherProps }) => {
45 | const [hover, setHover] = useState(false)
46 |
47 | return (
48 | setHover(true)} onMouseLeave={e => setHover(false)}>
49 |
50 |
51 |
52 |
53 |
61 |
69 |
70 |
71 | )
72 | }
73 |
74 | export default Components
75 |
--------------------------------------------------------------------------------
/client/src/config/api.js:
--------------------------------------------------------------------------------
1 | export const devConfig = {
2 | url: process.env.REACT_APP_DEV_CLIENT,
3 | apiUrl: process.env.REACT_APP_DEV_API,
4 | chatUrl: process.env.REACT_APP_DEV_CHAT,
5 | kakaoKey: process.env.REACT_APP_DEV_KAKAO_KEY,
6 | kakaoOAuthKey: process.env.REACT_APP_DEV_OAUTH_KAKAO_KEY,
7 | googleOAuthKey: process.env.REACT_APP_DEV_OAUTH_GOOGLE_KEY
8 | };
9 |
10 | export const prodConfig = {
11 | url: process.env.REACT_APP_CLIENT,
12 | apiUrl: process.env.REACT_APP_API,
13 | chatUrl: process.env.REACT_APP_CHAT,
14 | kakaoKey: process.env.REACT_APP_KAKAO_KEY,
15 | kakaoOAuthKey: process.env.REACT_APP_OAUTH_KAKAO_KEY,
16 | googleOAuthKey: process.env.REACT_APP_OAUTH_GOOGLE_KEY
17 | };
18 |
19 | export default process.env.NODE_ENV === "development" ? devConfig : prodConfig;
20 |
--------------------------------------------------------------------------------
/client/src/config/firebase.js:
--------------------------------------------------------------------------------
1 | export const devConfig = {
2 | apiKey: process.env.REACT_APP_DEV_API_KEY,
3 | authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
4 | databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
5 | projectId: process.env.REACT_APP_DEV_PROJECT_ID,
6 | storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
7 | messagingSenderId: process.env.REACT_DEV_APP_MESSAGING_SENDERID,
8 | appId: process.env.REACT_APP_DEV_APP_ID,
9 | measurementId: process.env.REACT_APP_DEV_MEASUREMENT_ID
10 | }
11 |
12 | export const prodConfig = {
13 | apiKey: process.env.REACT_APP_API_KEY,
14 | authDomain: process.env.REACT_APP_AUTH_DOMAIN,
15 | databaseURL: process.env.REACT_APP_DATABASE_URL,
16 | projectId: process.env.REACT_APP_PROJECT_ID,
17 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
18 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDERID,
19 | appId: process.env.REACT_APP_APP_ID,
20 | measurementId: process.env.REACT_APP_MEASUREMENT_ID
21 | }
22 |
23 | export default process.env.NODE_ENV === "development" ? devConfig : prodConfig;
--------------------------------------------------------------------------------
/client/src/config/path.js:
--------------------------------------------------------------------------------
1 | export default {
2 | storage: {
3 | image: "/api/storage/image",
4 | profile: "/api/storage/profile"
5 | },
6 | bids: "/api/bids",
7 | logfilter: "/api/log/filter",
8 | sign: {
9 | in: "/api/sign/login",
10 | out: "/api/sign/logout",
11 | kakao: "/api/sign/kakao",
12 | google: "/api/sign/google"
13 | },
14 | products: "/api/products",
15 | productsWithBids: "/api/products/withBids",
16 | productsRating: "/api/products/rating",
17 | users: "/api/users",
18 | userid: "/api/users/idx",
19 | items: {
20 | category: "/api/items/category",
21 | hot: "/api/items/hot",
22 | deadline: "/api/items/deadline",
23 | related: "/api/items/related"
24 | },
25 | statics: {
26 | categories: "/api/statics/categories"
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/client/src/constants/strings.js:
--------------------------------------------------------------------------------
1 | export const message = {
2 | serverBusy: "Sorry, Server Is too Busy"
3 | }
4 |
5 | export const notice = {
6 | successRegister: "정상적으로 등록되었습니다."
7 | }
--------------------------------------------------------------------------------
/client/src/constants/values.js:
--------------------------------------------------------------------------------
1 | export const limits = {
2 | productTitle: 50,
3 | productContent: 1000
4 | }
--------------------------------------------------------------------------------
/client/src/context/MessengerContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const MessengerContext = React.createContext();
4 |
5 | export default MessengerContext;
6 |
--------------------------------------------------------------------------------
/client/src/context/ModalContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ModalContext = React.createContext();
4 |
5 | export default ModalContext;
6 |
--------------------------------------------------------------------------------
/client/src/context/NotificationContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const NotificationContext = React.createContext();
4 |
5 | export default NotificationContext;
6 |
--------------------------------------------------------------------------------
/client/src/context/ProductPageContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ProductPageContext = React.createContext();
4 |
5 | export default ProductPageContext;
6 |
--------------------------------------------------------------------------------
/client/src/context/SocketContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const SocketContext = React.createContext();
4 |
5 | export default SocketContext;
6 |
--------------------------------------------------------------------------------
/client/src/context/UserContext.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const UserContext = React.createContext();
4 |
5 | export default UserContext;
6 |
--------------------------------------------------------------------------------
/client/src/data/detail-category-list.js:
--------------------------------------------------------------------------------
1 | const detailCategoryList = [
2 | {
3 | idx: 1,
4 | details: [
5 | { title: "남성의류", code: 1001 },
6 | { title: "여성의류", code: 1002 },
7 | { title: "아동의류", code: 1003 }
8 | ]
9 | },
10 | {
11 | idx: 2,
12 | details: [
13 | { title: "컴퓨터", code: 2001 },
14 | { title: "휴대폰", code: 2002 },
15 | { title: "카메라", code: 2003 }
16 | ]
17 | },
18 | {
19 | idx: 3,
20 | details: [
21 | { title: "도서", code: 3001 },
22 | { title: "문구", code: 3002 }
23 | ]
24 | }
25 | ]
26 |
27 | export default detailCategoryList
28 |
--------------------------------------------------------------------------------
/client/src/hooks/useFetch.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import apiConfig from "../config/api";
3 | import axios from "axios";
4 |
5 | const { apiUrl } = apiConfig;
6 |
7 |
8 | export const useFetch = (path, handleFetchSuccess, handleFetchError) => {
9 | React.useEffect(() => {
10 | let isUnmounted = false;
11 |
12 | const fetchData = async () => {
13 | try {
14 | const response = await axios.get(`${apiUrl}${path}`);
15 | if (!isUnmounted) {
16 | handleFetchSuccess(response);
17 | }
18 | } catch (error) {
19 | if (!isUnmounted) {
20 | handleFetchError(error);
21 | }
22 | }
23 | };
24 | fetchData();
25 |
26 | return () => (isUnmounted = true);
27 | }, []);
28 | };
29 |
--------------------------------------------------------------------------------
/client/src/hooks/usePrevious.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 |
3 | export const usePrevious = value => {
4 | const ref = useRef();
5 |
6 | useEffect(() => {
7 | ref.current = value;
8 | }, [value]);
9 |
10 | return ref.current;
11 | };
12 |
--------------------------------------------------------------------------------
/client/src/hooks/useSocket.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import io from "socket.io-client";
3 | import apiConfig from "../config/api";
4 |
5 | const { chatUrl } = apiConfig;
6 |
7 | export const useSocket = (user, setNotifications) => {
8 | const [socket, setSocket] = useState(null);
9 |
10 | useEffect(() => {
11 | if (Object.keys(user).length === 0) return;
12 |
13 | const socket = io(chatUrl);
14 |
15 | socket.on("connect", () => {
16 | setSocket(socket);
17 | });
18 |
19 | socket.on("auctionResult", ({ type, product }) => {
20 | setNotifications(notis => [...notis, { type, product }]);
21 | });
22 |
23 | return () => {
24 | socket.close();
25 | };
26 | }, [user]);
27 |
28 | return { socket };
29 | };
30 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './style/index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: https://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/client/src/mock/deadline-items/deadline-items.js:
--------------------------------------------------------------------------------
1 | const deadlines = [
2 | { "id": 1, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827762-7ac1fe00-06e6-11ea-9c20-76e7d77e6d76.jpg", "title": "고양이 발자국", "isAuction": true, "date": "2019-11-23", "bids": 11, "buyNowPrice": 35000, "topBid": 14000 },
3 | { "id": 2, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827774-86152980-06e6-11ea-8f16-4e026ca02b73.jpg", "title": "젤리", "isAuction": true, "date": "2019-11-23", "bids": 3, "buyNowPrice": null, "topBid": 7000 },
4 | { "id": 3, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827782-9200eb80-06e6-11ea-888b-ff1d46448147.jpg", "title": "곤란한 고양이", "isAuction": true, "date": "2019-11-24", "bids": 5, "buyNowPrice": null, "topBid": 12000 },
5 | { "id": 4, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827808-a644e880-06e6-11ea-9729-bb2188344e78.png", "title": "뭘보냐는 고양이", "isAuction": true, "date": "2019-11-24", "bids": 2, "buyNowPrice": 13000, "topBid": 3500 },
6 | { "id": 5, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827823-b066e700-06e6-11ea-97e5-74b09cf54625.jpg", "title": "아련한 고양이", "isAuction": true, "date": "2019-11-25", "bids": 0, "buyNowPrice": 8000, "topBid": 0 }
7 | ]
8 |
9 | export default deadlines;
--------------------------------------------------------------------------------
/client/src/mock/index.jsx:
--------------------------------------------------------------------------------
1 | import deadlines from './deadline-items/deadline-items';
2 | import populars from './popular-items/popular-items';
3 |
4 | export { deadlines, populars }
--------------------------------------------------------------------------------
/client/src/mock/myitems/myitems.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | title: "임시데이터 방금 전",
4 | thumbnail:
5 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
6 | status: "경매중",
7 | price: 3000,
8 | time: new Date(`2019-11-28 18:18:00`)
9 | },
10 | {
11 | title: "임시데이터 몇분 전",
12 | thumbnail:
13 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
14 | status: "경매중",
15 | price: 3000,
16 | time: new Date(`2019-11-28 18:00:00`)
17 | },
18 | {
19 | title: "임시데이터 몇 시간 전",
20 | thumbnail:
21 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
22 | status: "경매중",
23 | price: 3000,
24 | time: new Date(`2019-11-28 15:00:01`)
25 | },
26 | {
27 | title: "임시데이터 몇 일전",
28 | thumbnail:
29 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
30 | status: "경매중",
31 | price: 3000,
32 | time: new Date(`2019-11-23 00:00:00`)
33 | },
34 | {
35 | title: "임시데이터 몇 개월 전",
36 | thumbnail:
37 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
38 | status: "경매중",
39 | price: 3000,
40 | time: new Date(`2019-08-23 00:00:01`)
41 | },
42 | {
43 | title: "임시데이터 몇 년전",
44 | thumbnail:
45 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
46 | status: "경매중",
47 | price: 3000,
48 | time: new Date(`2013-11-23 00:00:01`)
49 | }
50 | ]
51 |
52 |
--------------------------------------------------------------------------------
/client/src/mock/popular-items/popular-items.js:
--------------------------------------------------------------------------------
1 | const populars = [
2 | { "id": 6, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827678-30d91800-06e6-11ea-83fa-2afd1ada89aa.jpg", "title": "화가난 고양이", "isAuction": true, "date": "2019-11-26", "bids": 13, "buyNowPrice": 95000, "topBid": 54000 },
3 | { "id": 7, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827706-477f6f00-06e6-11ea-946f-ad123880bc54.jpg", "title": "비행하는 고양이", "isAuction": true, "date": "2019-11-27", "bids": 9, "buyNowPrice": null, "topBid": 34000 },
4 | { "id": 8, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827717-5108d700-06e6-11ea-86c4-81465e6efa85.jpg", "title": "토끼귀 고양이", "isAuction": true, "date": "2019-11-25", "bids": 8, "buyNowPrice": null, "topBid": 27000 },
5 | { "id": 9, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827731-5e25c600-06e6-11ea-9d7e-ee914a85dbdb.png", "title": "웃는 고양이", "isAuction": true, "date": "2019-11-26", "bids": 6, "buyNowPrice": 15000, "topBid": 12000 },
6 | { "id": 10, "thumbnail": "https://user-images.githubusercontent.com/37038262/68827745-67af2e00-06e6-11ea-9787-ff303548ac1d.png", "title": "집사를 깨우는 고양이", "isAuction": true, "date": "2019-11-25", "bids": 0, "buyNowPrice": 3000, "topBid": 0 }
7 | ]
8 |
9 | export default populars;
--------------------------------------------------------------------------------
/client/src/pages/CategoryItems/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import styled from "styled-components";
3 | import CardContainer from "../../components/Molecules/CardContainer";
4 |
5 | import apiConfig from "../../config/api";
6 | import pathConfig from "../../config/path";
7 | import ErrorPage from "../ErrorPage";
8 | import { getFetch } from "../../services/fetchService";
9 | const { apiUrl } = apiConfig;
10 | const { items, statics } = pathConfig;
11 |
12 | const MainStyle = styled.div`
13 | display: flex;
14 | font-family: "BMJUA";
15 | width: 100%;
16 | flex-direction: column;
17 | justify-content: center;
18 | .category {
19 | display: flex;
20 | font-size: xx-large;
21 | justify-content: flex-start;
22 | padding-left: 10rem;
23 | }
24 | `;
25 |
26 | const CategoryItems = ({ match }) => {
27 | const categoryCode = Number(match.params.code);
28 | const [itemlist, setItemslist] = useState([]);
29 | const [title, setTitle] = useState("");
30 |
31 | const getCategoryList = async () => {
32 | const result = await getFetch(
33 | `${apiUrl}${statics.categories}/${categoryCode}`,
34 | {},
35 | { code: categoryCode }
36 | );
37 | setTitle(result.title);
38 | };
39 |
40 | const fetcher = async () => {
41 | setItemslist([]);
42 | const url = `${apiUrl}${items.category}/${categoryCode}`;
43 | let result = await fetch(url);
44 | const list = await result.json();
45 |
46 | setItemslist(list[0]);
47 | };
48 | useEffect(() => {
49 | getCategoryList();
50 | fetcher();
51 | }, [categoryCode]);
52 |
53 | return (
54 | <>
55 | {title && title.length ? (
56 |
57 |
58 |
59 | ) : (
60 |
61 | )}
62 | >
63 | );
64 | };
65 |
66 | export default CategoryItems;
67 |
--------------------------------------------------------------------------------
/client/src/pages/ErrorPage/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import NotFoundImg from "../../assets/notFound.png";
4 |
5 | const Wrap = styled.div`
6 | width: 100%;
7 | margin: auto 0;
8 | display: flex;
9 | justify-content: center;
10 | font-family: "BMJUA";
11 | `;
12 | const Contents = styled.div`
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | text-align: right;
17 | `;
18 | const Image = styled.div``;
19 |
20 | const Error = () => {
21 | return (
22 |
23 |
24 | 요청한 페이지를 찾을 수 없습니다.
25 | 존재하지 않는 주소를 입력하셨거나,
26 | 요청하신 페이지의 주소가 변경, 삭제되어 찾을 수 없습니다.
27 | 입력하신 주소가 정확한지 다시 한 번 확인해 주시길 바랍니다.
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Error;
37 |
--------------------------------------------------------------------------------
/client/src/pages/Main/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import styled from "styled-components";
3 | import CardContainer from "../../components/Molecules/CardContainer";
4 | import apiConfig from "../../config/api";
5 | import pathConfig from "../../config/path";
6 | const { apiUrl } = apiConfig;
7 | const { items } = pathConfig;
8 |
9 | const MainStyle = styled.div`
10 | display: flex;
11 | font-family: "BMJUA";
12 | width: 100%;
13 | flex-direction: column;
14 | justify-content: center;
15 | .category {
16 | display: flex;
17 | font-size: xx-large;
18 | justify-content: flex-start;
19 | padding-left: 10rem;
20 | }
21 | `;
22 |
23 | const Main = () => {
24 | const [popular, setPopular] = useState([]);
25 | const [deadline, setDeadline] = useState([]);
26 |
27 | const getPopularList = () => {
28 | const url = `${apiUrl}${items.hot}`;
29 | fetch(url)
30 | .then(result => result.json())
31 | .then(result => {
32 | setPopular(result);
33 | });
34 | };
35 |
36 | const getDeadLineList = () => {
37 | const url = `${apiUrl}${items.deadline}`;
38 | fetch(url)
39 | .then(result => result.json())
40 | .then(result => setDeadline(result));
41 | };
42 |
43 | useEffect(() => {
44 | getPopularList();
45 | getDeadLineList();
46 | }, []);
47 |
48 | return (
49 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | export default Main;
57 |
--------------------------------------------------------------------------------
/client/src/pages/MyItems/contants.jsx:
--------------------------------------------------------------------------------
1 | export const limits = 10
2 |
3 | export default {
4 | limits
5 | }
6 |
--------------------------------------------------------------------------------
/client/src/pages/ProductUpdate/constants.jsx:
--------------------------------------------------------------------------------
1 | export const validDialog = {
2 | title: "확인",
3 | content: "해당 정보로 수정하시겠습니까?",
4 | cancelAble: true
5 | }
6 |
7 | export const invalidDialog = {
8 | title: "경고",
9 | content: "입력 값 중 빈 값이 있습니다.",
10 | cancelAble: false
11 | }
12 |
--------------------------------------------------------------------------------
/client/src/pages/Register/constants.jsx:
--------------------------------------------------------------------------------
1 | export const phaseList = ["Step 1. 카테고리", "Step 2. 상품등록", "Step 3. 완료"]
2 |
3 | export const termList = [
4 | { title: "1일", term: 1 },
5 | { title: "2일", term: 2 },
6 | { title: "3일", term: 3 },
7 | { title: "4일", term: 4 },
8 | { title: "5일", term: 5 },
9 | { title: "6일", term: 6 },
10 | { title: "1주", term: 7 }
11 | ]
12 |
13 | export const categoryList = {
14 | leftTitle: "카테고리",
15 | rightTitle: "상세 카테고리",
16 | leftList: ["의류", "가전", "생활"],
17 | rightList: [
18 | ["남성의류", "여성의류", "아동의류"],
19 | ["컴퓨터", "휴대폰", "카메라"],
20 | ["도서", "문구"]
21 | ]
22 | }
23 |
24 | export const itemDescription = [
25 | "경매시 즉시 구매가는 변동 될 수 있습니다.",
26 | "경매가 시작되는 가격입니다.",
27 | "낙찰 예상가를 적어주세요. 낙찰가와 차이를 알려드립니다."
28 | ]
29 |
30 | export const dialogOption = {
31 | title: "알림",
32 | content: "해당 정보로 등록하시겠습니까?"
33 | }
34 |
35 | export const defaultData = {
36 | title: "",
37 | contents: "",
38 | thumbnail: "",
39 | images: [],
40 | nowPrice: undefined,
41 | hopePrice: undefined,
42 | minPrice: undefined,
43 | endDate: "",
44 | categoryCode: undefined
45 | }
46 |
47 | export default { phaseList, termList, dialogOption }
48 |
--------------------------------------------------------------------------------
/client/src/pages/Register/context.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export const context = React.createContext({
4 | data: {},
5 | callback: undefined
6 | })
7 |
8 | export default context
9 |
--------------------------------------------------------------------------------
/client/src/pages/Register/template/Complete/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import styled from "styled-components";
3 |
4 | import ShareBox from "../../../../components/Molecules/ShareBox";
5 | import { notice } from "../../../../constants/strings";
6 | import apiConfig from "../../../../config/api";
7 |
8 | import productContext from "../../context";
9 |
10 | const PageBase = styled.div`
11 | width: 80%;
12 |
13 | box-sizing: border-box;
14 | `;
15 |
16 | const ContentDiv = styled.div`
17 | width: 80%;
18 | margin: 0 auto;
19 | `;
20 |
21 | const ButtonDiv = styled.div`
22 | width: 15rem;
23 | height: 14rem;
24 | display: flex;
25 | flex-direction: column;
26 | justify-content: space-between;
27 | margin: 0 auto;
28 | text-align: center;
29 | `;
30 |
31 | const LinkBox = styled.a`
32 | width: 100%;
33 | height: 3rem;
34 | border: none;
35 | background: var(--color-secondary-plus1);
36 | font-size: var(--font-size-xl);
37 | border-radius: 1.5rem;
38 | text-decoration: none;
39 | color: white;
40 | line-height: 3rem;
41 | transition: background 0.15s ease-in-out;
42 |
43 | &:hover,
44 | &:focus {
45 | background: var(--color-secondary-plus1-lighter);
46 | font-weight: 700;
47 | }
48 | `;
49 |
50 | const ShareDiv = styled.div`
51 | width: fit-content;
52 | height: fit-content;
53 | margin: 20px auto 30px auto;
54 | `;
55 |
56 | const NoticeDiv = styled.div`
57 | width: 400px;
58 | height: 200px;
59 | font-family: "BMDOHYEON";
60 | font-size: var(--font-size-xl);
61 | display: flex;
62 | justify-content: center;
63 | align-items: center;
64 | text-align: center;
65 | border: var(--color-secondary-plus1) dashed 1px;
66 | margin: 20px auto 50px auto;
67 | `;
68 |
69 | const NoticeText = styled.div`
70 | width: 200px;
71 | word-break: keep-all;
72 | `;
73 |
74 | const Component = () => {
75 | const obj = useContext(productContext).data;
76 |
77 | return (
78 |
79 |
80 |
81 | {notice.successRegister}
82 |
83 |
84 |
85 |
86 |
87 | 상품 확인
88 | 계속 등록
89 | 마이페이지로
90 | 확인
91 |
92 |
93 |
94 | );
95 | };
96 |
97 | export default Component;
98 |
--------------------------------------------------------------------------------
/client/src/pages/Register/template/SelectCategory/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import styled from "styled-components";
3 |
4 | import Button from "../../../../components/Atoms/BoxButton";
5 | import CategorySelector from "../../../../components/Organism/ItemCategorySelector";
6 |
7 | import productContext from "../../context";
8 |
9 | import { categoryList } from "../../constants.jsx";
10 | import { idxNotSelected } from "../../../../utils/validator.js";
11 |
12 | const PageBase = styled.div`
13 | width: 80%;
14 |
15 | box-sizing: border-box;
16 | `;
17 |
18 | const ContentDiv = styled.div`
19 | width: 60%;
20 | margin: 0 auto;
21 | `;
22 |
23 | const ButtonContainer = styled.div`
24 | width: 100%;
25 | height: 3em;
26 | display: flex;
27 | justify-content: flex-end;
28 | margin: 1rem 0;
29 | `;
30 |
31 | const validation = (result, successCallback, failCallback) => {
32 | const isInvalid = result.some(value => value);
33 | isInvalid ? failCallback() : successCallback();
34 | };
35 |
36 | const Component = ({ next, leftList, rightList }) => {
37 | const obj = useContext(productContext).data;
38 |
39 | const [leftIdx, setLeftIdx] = useState(-1);
40 | const [rightIdx, setRightIdx] = useState(-1);
41 |
42 | const valiResult = [idxNotSelected(leftIdx), idxNotSelected(rightIdx)];
43 |
44 | const successCallback = () => {
45 | obj.mainCategory = leftList[leftIdx];
46 | obj.subCategory = rightList[leftIdx][rightIdx];
47 | next();
48 | };
49 |
50 | const failCallback = () => {
51 | alert("선택되지 않은 값이 있습니다.");
52 | };
53 |
54 | return (
55 |
56 |
57 |
67 |
68 |
75 |
76 |
77 | );
78 | };
79 |
80 | export default Component;
81 |
--------------------------------------------------------------------------------
/client/src/services/fetchService.js:
--------------------------------------------------------------------------------
1 | import { Await, Option } from "../utils/fetchUtil.js";
2 | import axios from "axios";
3 |
4 | export const getFetch = async (url, headerOption, params) => {
5 | const option = Option.get;
6 | option.params = params;
7 | option.headers = Object.assign(option.headers, headerOption);
8 | const result = await Await(url, option);
9 |
10 | return result;
11 | };
12 |
13 | export const postJsonFetch = async (url, headerOption, body) => {
14 | const option = Option.postJson;
15 | option.body = JSON.stringify(body);
16 | option.headers = Object.assign(option.headers, headerOption);
17 | const result = await Await(url, option);
18 |
19 | return result;
20 | };
21 |
22 | export const putJsonFetch = async (url, headerOption, body) => {
23 | const option = Option.putJson;
24 | option.body = JSON.stringify(body);
25 | option.headers = Object.assign(option.headers, headerOption);
26 | const result = await Await(url, option);
27 |
28 | return result;
29 | };
30 |
31 | export const deleteJsonFetch = async (url, headerOption, body) => {
32 | const option = await axios.delete(url, body, headerOption);
33 | return option;
34 | };
35 |
36 | export default { getFetch, postJsonFetch, putJsonFetch, deleteJsonFetch };
37 |
--------------------------------------------------------------------------------
/client/src/services/imageService.js:
--------------------------------------------------------------------------------
1 | import Resizer from 'react-image-file-resizer';
2 |
3 | const SIZE = 250
4 |
5 | export const createThumbnail = async (file) => {
6 | return new Promise(resolve => {
7 | Resizer.imageFileResizer(
8 | file,
9 | SIZE,
10 | SIZE,
11 | 'JPEG',
12 | 100,
13 | 0,
14 | uri => resolve(uri.split(',')[1])
15 | ,
16 | 'base64'
17 | )
18 | });
19 | }
20 |
21 | export default { createThumbnail }
--------------------------------------------------------------------------------
/client/src/stories/3-Progress.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ProgressButton from '../components/Organism/RegisterProgress/Button'
4 | import RegisterProgress from '../components/Organism/RegisterProgress'
5 |
6 | export default {
7 | title: 'Progress',
8 | };
9 |
10 | export const button = () => {
11 | return (
12 |
13 | )
14 | }
15 |
16 | export const activeButton = () => {
17 | return (
18 |
19 | )
20 | }
21 |
22 | export const registerProgress = () => {
23 | return (
24 |
25 | )
26 | }
--------------------------------------------------------------------------------
/client/src/stories/5-Carousel.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Carousel from '../components/Molecules/Carousel'
4 |
5 | export default {
6 | title: 'Carousel',
7 | };
8 |
9 | export const carousel = () => {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/6-SelectBox.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import SelectBox from '../components/Molecules/SelectBox'
4 | import ListItem from '../components/Molecules/SelectBox/ListItem'
5 | import SelectList from '../components/Molecules/SelectBox/List'
6 |
7 | export default {
8 | title: 'SelectBox',
9 | };
10 |
11 | export const listItem = () => {
12 |
13 | return (
14 | <>
15 |
16 |
17 | >
18 | )
19 |
20 | }
21 |
22 | export const selectList = () => {
23 | const dummy = ['아이템 1', '아이템 2', '아이템 3']
24 | const showCount = 5;
25 | return (
26 |
27 | )
28 | }
29 |
30 | export const selectBox = () => {
31 |
32 | const dummy = ['아이템 1', '아이템 2', '아이템 3']
33 | const dummy2 = ['아이템 1', '아이템 2', '아이템 3', '아이템 4', '아이템 5', '아이템 6']
34 |
35 | return (
36 | <>
37 |
38 | None Scroll
39 |
40 | Scroll
41 |
42 | >
43 | )
44 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/BoxButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NextButton from '../../components/Atoms/BoxButton'
4 |
5 | export default {
6 | title: 'Atoms|BoxButton',
7 | };
8 |
9 | export const boxButton = () => {
10 | return (
11 | alert('Hello')} />
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/Card.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Card from '../../components/Atoms/Card';
4 | import data from '../../mock/popular-items/popular-items'
5 |
6 | export default {
7 | title: 'Atoms|Card',
8 | };
9 |
10 | // export const card = () => {
11 | // return (
12 | // <>
13 | //
14 | // >
15 | // )
16 | // }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/Footer.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Footer from '../../components/Atoms/Footer'
4 |
5 | export default {
6 | title: 'Atoms|Footer',
7 | };
8 |
9 | export const footer = () => {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/Header.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Header from '../../components/Atoms/Header'
4 |
5 | export default {
6 | title: 'Atoms|Header',
7 | };
8 |
9 | export const header = () => {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/InputWithLimits.stories.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | import InputBox from '../../components/Atoms/InputWithLimit'
4 |
5 | export default {
6 | title: 'Atoms|inputWithLimits',
7 | };
8 |
9 | // export const inputBox = () => {
10 |
11 | // const [value, setValue] = useState('값')
12 |
13 | // return (
14 | //
15 | // )
16 | // }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/LoadingBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import LoadingBar from '../../components/Atoms/LoadingBar'
4 |
5 | export default {
6 | title: 'Atoms|LoadingBar',
7 | };
8 |
9 | export const loadingBar = () => {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/Padding.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({
4 | title: 'Styles|Padding&Margin'
5 | })
6 |
7 | export const paddings = () => {
8 | return (
9 | <>
10 | 다람쥐 헌 쳇바퀴에 타고파.
11 | 다람쥐 헌 쳇바퀴에 타고파.
12 | 다람쥐 헌 쳇바퀴에 타고파.
13 | 다람쥐 헌 쳇바퀴에 타고파.
14 | 다람쥐 헌 쳇바퀴에 타고파.
15 | 다람쥐 헌 쳇바퀴에 타고파.
16 | >
17 | )
18 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/Text.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default ({
4 | title: 'Styles|Font'
5 | })
6 |
7 | export const fonts = () => {
8 | return (
9 | <>
10 | 다람쥐 헌 쳇바퀴에 타고파.
11 | 다람쥐 헌 쳇바퀴에 타고파.
12 | 다람쥐 헌 쳇바퀴에 타고파.
13 | 다람쥐 헌 쳇바퀴에 타고파.
14 | 다람쥐 헌 쳇바퀴에 타고파.
15 | 다람쥐 헌 쳇바퀴에 타고파.
16 | 다람쥐 헌 쳇바퀴에 타고파.
17 | >
18 | )
19 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/TextareaWithLength.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Textarea from '../../components/Atoms/TextareaWithLength'
4 |
5 | export default ({
6 | title: 'Atoms|TextareaWithLength'
7 | })
8 |
9 | export const itemDescription = () => {
10 | return (
11 |
12 | )
13 | }
--------------------------------------------------------------------------------
/client/src/stories/Atoms/ToggleButton.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ToggleButton from '../../components/Atoms/ToggleButton'
4 |
5 | export default ({
6 | title: 'Atoms|ToggleButton'
7 | })
8 |
9 | export const nonChecked = () => {
10 | return (
11 |
12 | )
13 | }
14 |
15 | export const checked = () => {
16 | return (
17 |
18 | )
19 | }
--------------------------------------------------------------------------------
/client/src/stories/Modal/AlretDialog.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import AlertDialog from '../../components/Molecules/AlertDialog'
4 |
5 | export default ({
6 | title: 'Modal|Alert'
7 | })
8 |
9 | export const alertDialog = () => {
10 | return (
11 |
12 | )
13 | }
14 |
15 | export const cancelDialog = () => {
16 | return (
17 |
18 | )
19 | }
--------------------------------------------------------------------------------
/client/src/stories/Modal/ReportDialog.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import ReportDialog from "../../components/Molecules/ReportDialog";
4 | import ReportButton from "../../components/Atoms/ReportButton";
5 | export default {
6 | title: "Modal|Report"
7 | };
8 | export const ReportDialogButtonToProduct = () => {
9 | return (
10 | <>
11 |
12 | >
13 | );
14 | };
15 | export const ReportDialogButtonToUser = () => {
16 | return (
17 | <>
18 |
19 | >
20 | );
21 | };
22 |
23 | export const ReportDialogDemo = () => {
24 | return (
25 | <>
26 |
27 | >
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/client/src/stories/Molecules/InfiniteScroll.stories.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react"
2 |
3 | import TradeBox from "../../components/Organism/TradeBox"
4 | import InfiniteScroll from "../../components/Molecules/InfiniteScroll"
5 |
6 | export default {
7 | title: "Molecules|InfiniteScroll"
8 | }
9 |
10 | const tradeDummy = [
11 | {
12 | title: "임시데이터1",
13 | thumbnail:
14 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
15 | status: "경매중",
16 | price: 3000
17 | },
18 | {
19 | title: "임시데이터2",
20 | thumbnail:
21 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
22 | status: "경매중",
23 | price: 3000
24 | },
25 | {
26 | title: "임시데이터3",
27 | thumbnail:
28 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
29 | status: "경매중",
30 | price: 3000
31 | },
32 | {
33 | title: "임시데이터4",
34 | thumbnail:
35 | "https://post-phinf.pstatic.net/MjAxODA0MDNfMjgy/MDAxNTIyNjgxNjQzMTc2.9zObByVQ-Az9SuNbnhDA34JAkBHBgBL0zh2xjibG8cIg.s9M1q3XTHMUBXLY1RuDZ7h40YZGu8RpXAEcTk4lKCxog.JPEG/bjsn-20171130-195451-000-resize.jpg?type=w1200",
36 | status: "경매중",
37 | price: 3000
38 | }
39 | ]
40 |
41 | export const infiniteScroll = () => {
42 | const fakeFetch = (delay = 1000) =>
43 | new Promise((resolve, reject) => {
44 | setTimeout(() => {
45 | resolve(tradeDummy)
46 | }, delay)
47 | })
48 |
49 | const drawer = item => item.map(value => )
50 |
51 | return (
52 | <>
53 |
54 | >
55 | )
56 | }
57 |
--------------------------------------------------------------------------------
/client/src/stories/Molecules/ShareButtonBox.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import ShareButtonBox from '../../components/Molecules/ShareBox'
4 |
5 | export default ({
6 | title: 'Molecules|ShareButtonBox'
7 | })
8 |
9 | export const itemDescription = () => {
10 |
11 | return (
12 |
13 | )
14 | }
--------------------------------------------------------------------------------
/client/src/stories/Molecules/TermSelector.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import RegisterTermSelector from '../../components/Organism/RegisterTermSelector'
4 |
5 | export default ({
6 | title: 'Organisms|RegisterTermSelector'
7 | })
8 |
9 | export const itemDescription = () => {
10 |
11 | const data = [['Hello', 'Hallo', 'Aloha']]
12 |
13 | return (
14 |
15 | )
16 | }
--------------------------------------------------------------------------------
/client/src/stories/Molecules/TradeResultBox.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | import TradeBox from "../../components/Organism/TradeBox"
4 |
5 | export default {
6 | title: "Molecules|TradeResultBox"
7 | }
8 |
9 | export const tradeResultBox = props => {
10 | return (
11 |
20 | )
21 | }
22 |
23 | export const tradeResultBoxNot = props => {
24 | return
25 | }
26 |
27 | export const myItem = props => {
28 | return (
29 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/client/src/stories/Organisms/CategoryBar.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import CategoryIcon from "../../components/Organism/CategoryBar/CategoryIcon"
4 | import TextIcon from "../../components/Organism/CategoryBar/CategoryIcon/TextIcon";
5 | import ImageIcon from "../../components/Organism/CategoryBar/CategoryIcon/ImageIcon";
6 |
7 | export default {
8 | title: "Organisms|Category"
9 | };
10 |
11 | export const texticon = () => {
12 | return (
13 |
14 | {"Hello World"}
15 |
16 | );
17 | };
18 |
19 | export const imageicon = () => {
20 | return (
21 |
22 |
27 |
28 | );
29 | };
30 |
31 | export const categoryIcon = () => {
32 | return (
33 |
34 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/client/src/stories/Organisms/Chats.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ChatSend from "../../components/Organism/Chat/ChatSend";
3 | import Chat from "../../components/Organism/Chat/Chat";
4 |
5 | export default {
6 | title: "Organisms|Chats"
7 | };
8 |
9 | export const ChatSendComponent = () => {
10 | return ;
11 | };
12 |
13 | const chat = {
14 | type: "message",
15 | id: "JJajangMyeon",
16 | src: "https://i.pravatar.cc/150?img=10",
17 | text: "화요일은 짜장면을 먹어보면 어떨까요?? 맛있겠다."
18 | };
19 |
20 | export const ChatComponent = () => {
21 | return ;
22 | };
23 |
--------------------------------------------------------------------------------
/client/src/stories/Organisms/Messenger.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import CreateButton from "../../components/Messenger/CreateButton"
3 | import RoomElement from "../../components/Messenger/Container/RoomElement"
4 | import ChatMessage from "../../components/Messenger/Container/ChatContainer/ChatMessage"
5 | import MessengerDemo from "../../components/Messenger/Container/ChatContainer"
6 |
7 | export default {
8 | title: "Organisms|Messenger"
9 | }
10 |
11 | //방만들기 버튼
12 | export const MessengerCreateButton = () => (
13 |
14 | )
15 |
16 | //메시지 목록 요소
17 |
18 | export const MessengerRoomElement = () => {
19 | return
20 | }
21 | export const MessengerRoomElementBigmessage = () => {
22 | return (
23 |
28 | )
29 | }
30 |
31 | //메시지 요소
32 | export const MessengerReceiveElement = () => {
33 | return (
34 |
35 |
36 |
37 | )
38 | }
39 | export const MessengerSendElement = () => {
40 | return (
41 |
42 |
43 |
44 | )
45 | }
46 |
47 | export const MessengerListDemo = () => {
48 | return (
49 |
50 |
51 |
56 |
57 |
58 | )
59 | }
60 | export const MessengerChatDemo = () => {
61 | return (
62 |
63 |
64 |
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/client/src/stories/Organisms/Products.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import "../../style/App.css";
4 | import "../../style/index.css";
5 |
6 | import AuctionGraph from "../../components/Organism/AuctionGraph";
7 | import ProductInfo from "../../components/Organism/ProductInfo";
8 |
9 | export default {
10 | title: "Organisms|Products"
11 | };
12 |
13 | export const AuctionGraphBox = () => {
14 | return ;
15 | };
16 |
17 | const product = {
18 | id: "12",
19 | src:
20 | "https://d1rkccsb0jf1bk.cloudfront.net/products/99993547/main/medium/gb05021_04-1454001319-8684.jpg",
21 | title: "애플 스마트 워치 3세대",
22 | seller: "최성찬",
23 | due: "1일 6시간 27분",
24 | price: "45,000"
25 | };
26 |
27 | export const ProductInfoBox = () => {
28 | return ;
29 | };
30 |
--------------------------------------------------------------------------------
/client/src/stories/TradeList.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ButtonSelect from "../components/Atoms/SelectOptionButton"
3 | import ButtonDays from "../components/Atoms/DayButton"
4 | import TradeListBox from "../components/Organism/TradeListBox"
5 |
6 | import { action } from "@storybook/addon-actions"
7 |
8 | export default {
9 | title: "TradeList"
10 | }
11 |
12 | export const TradeListSelectButton = () => (
13 |
14 |
15 |
16 | )
17 |
18 | export const TradeListDaysButton = () => (
19 |
20 |
21 |
22 | )
23 |
24 | export const tradeListBox = props => {
25 | return (
26 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/client/src/style/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "NanumGothic-Regular";
3 | font-style: normal;
4 | font-weight: normal;
5 | src: url("../assets/font/NanumGothic-Regular.ttf") format("truetype"),
6 | url("../assets/font/NanumGothic-Regular.woff") format("woff");
7 | }
8 |
9 | @font-face {
10 | font-family: "BMDOHYEON";
11 | src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/BMDOHYEON.woff")
12 | format("woff");
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | @font-face {
18 | font-family: "BMJUA";
19 | src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/BMJUA.woff") format("woff");
20 | font-weight: normal;
21 | font-style: normal;
22 | }
23 |
24 | body {
25 | margin: 0;
26 | font-family: "NanumGothic-Regular", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
27 | "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
28 | -webkit-font-smoothing: antialiased;
29 | -moz-osx-font-smoothing: grayscale;
30 | }
31 |
32 | code {
33 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
34 | }
35 |
36 | ::-webkit-scrollbar-track {
37 | border-radius: 0.25rem;
38 | background: none;
39 | }
40 |
41 | ::-webkit-scrollbar {
42 | width: 0.5rem;
43 | background: none;
44 | }
45 |
46 | ::-webkit-scrollbar-thumb {
47 | border-radius: 0.25rem;
48 | background-color: rgba(64, 64, 64, 0.3);
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/utils/dateUtil.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import "moment-timezone";
3 |
4 | moment.tz.setDefault("Asia/Seoul");
5 |
6 | export const getDiffDateTime = (end, start) => {
7 | const t1 = start ? moment(start) : moment();
8 | const t2 = moment(end)
9 | .utc()
10 | .format("YYYY-MM-DD HH:mm:ss");
11 | const diff = moment(t2).diff(t1);
12 |
13 | const d = moment.duration(diff).days();
14 | const h = moment.duration(diff).hours();
15 | const m = moment.duration(diff).minutes();
16 | const s = moment.duration(diff).seconds();
17 |
18 | return { diff, d, h, m, s };
19 | };
20 |
21 | export const getNowDateTime = () => moment().format("YYYY-MM-DD HH:mm:ss");
22 |
23 | export const toFormatDateTime = datestring =>
24 | moment(datestring)
25 | .utc()
26 | .format("YYYY-MM-DD HH:mm:ss");
27 |
28 | export const isTerminated = dateTime => {
29 | return moment().isAfter(toFormatDateTime(dateTime));
30 | };
31 |
--------------------------------------------------------------------------------
/client/src/utils/fetchUtil.js:
--------------------------------------------------------------------------------
1 | export const Async = (url, option, callback) => {
2 | fetch(url, option)
3 | .then((res) => {
4 | return res.json();
5 | }).then((json) => {
6 | if (callback !== undefined)
7 | callback(json)
8 | })
9 | }
10 |
11 | export const Await = async (url, option) => {
12 | return await fetch(url, option)
13 | .then((res) => {
14 | return res.json();
15 | }).then((json) => {
16 | return json;
17 | })
18 | }
19 |
20 | export const Option = {
21 | get: { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0' } },
22 | post: { method: 'POST', headers: { 'User-Agent': 'Mozilla/5.0' } },
23 | put: { methoe: 'PUT', headers: { 'User-Agent': 'Mozilla/5.0' } },
24 | postJson: { method: 'POST', headers: { 'User-Agent': 'Mozilla/5.0', 'Content-Type': 'application/json' } },
25 | putJson: { method: 'PUT', headers: { 'User-Agent': 'Mozilla/5.0', 'Content-Type': 'application/json' } }
26 | }
27 |
28 |
29 | export default { Async, Await, Option }
--------------------------------------------------------------------------------
/client/src/utils/validator.js:
--------------------------------------------------------------------------------
1 | export const strEmpty = (str) => !str || str.trim().length === 0
2 |
3 | export const idxNotSelected = (idx) => idx === -1
4 |
5 | export const isArrayEmpty = (list) => !list.length
6 |
7 | export const strLengthCheck = (str, maxLen) => str.length > maxLen
8 |
9 | export const isNumber = (key) => key.replace(/[a-zA-Zㄱ-ㅎㅏ-ㅣ가-힣]/g, '').length
10 |
11 | export default {
12 | strEmpty,
13 | idxNotSelected,
14 | isArrayEmpty,
15 | strLengthCheck,
16 | isNumber
17 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | mysql-server:
5 | image: mysql:5.7.28
6 | command: --character-set-server=utf8mb4
7 | --collation-server=utf8mb4_unicode_ci
8 | --lower_case_table_names=1
9 | --default-authentication-plugin=mysql_native_password
10 | volumes:
11 | - mysql:/var/lib/mysql
12 | ports:
13 | - "3306:3306"
14 | expose:
15 | - "3306"
16 | environment:
17 | - MYSQL_ROOT_PASSWORD=password
18 | - MYSQL_DATABASE=palda
19 | - MYSQL_USER=boost
20 | - MYSQL_PASSWORD=boost
21 | - MYSQL_ROOT_HOST=%
22 |
23 | api-server:
24 | build:
25 | context: ./server
26 | dockerfile: Dockerfile
27 | volumes:
28 | - ./server:/app:cached
29 | ports:
30 | - "3000:3000"
31 | environment:
32 | - NODE_ENV=development
33 | depends_on:
34 | - mysql-server
35 |
36 | chat-server:
37 | build:
38 | context: ./chat-server
39 | dockerfile: Dockerfile
40 | ports:
41 | - "4000:4000"
42 | environment:
43 | - NODE_ENV=development
44 |
45 | auction-server:
46 | build:
47 | context: ./auction-server
48 | dockerfile: Dockerfile
49 | ports:
50 | - "5000:5000"
51 | environment:
52 | - NODE_ENV=development
53 | volumes:
54 | mysql:
55 |
--------------------------------------------------------------------------------
/server/.env.example:
--------------------------------------------------------------------------------
1 | DB_TYPE=mysql
2 | DB_HOST= YOUR-HOST
3 | DB_DOCKER_COMPOSE_SERVICE_HOST=mysql-server
4 | DB_PORT= YOUR-PORT
5 | DB_USER= YOUR-USER
6 | DB_PASSWORD= YOUR-PASSWORD
7 | DB_NAME= YOUR-NAME
8 | GOOGLE_CLIENT_ID=
9 | GOOGLE_CLIENT_SECRET=
10 | JWT_KEY=
11 | KAKAO_API_KEY=
12 |
13 | ### Obeject Storeage
14 | END_POINT=https://kr.object.ncloudstorage.com
15 | REGION=kr-standard
16 | ACCESS_KEY=
17 | SECRET_KEY=
18 | DEV_BUCKET=
19 | BUCKET=
20 |
21 | ### Firebase
22 | FIRE_API_KEY=
23 | FIRE_AUTH_DOMAIN=
24 | FIRE_DATABASE_URL=
25 | FIRE_PROJECT_ID=
26 | FIRE_STORAGE_BUCKET=
27 | FIRE_MESSAGING_SENDERID=
28 | FIRE_APP_ID=
29 | FIRE_MEASUREMENT_ID=
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_STORE
3 | .env
4 | dist
5 |
6 | ## Authenticate
7 | keys
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 | WORKDIR /app
3 |
4 | RUN npm install -g pm2
5 | RUN npm install -g typescript
6 | RUN npm install -g cross-env
7 |
8 | COPY package*.json ./
9 | RUN npm install
10 |
11 | COPY . .
12 | EXPOSE 3000
13 | CMD [ "npm", "run", "dev" ]
--------------------------------------------------------------------------------
/server/ormconfig.js:
--------------------------------------------------------------------------------
1 | var CustomNamingStrategy_1 = require("./src/custom/CustomNamingStrategy");
2 | require('dotenv').config()
3 |
4 | module.exports = {
5 | type: process.env.DB_TYPE,
6 | host: process.env.DB_HOST,
7 | port: Number(process.env.DB_PORT),
8 | username: process.env.DB_USER,
9 | password: process.env.DB_PASSWORD,
10 | database: process.env.DB_NAME,
11 | logging: true,
12 | entities: [__dirname + "/src/models/*.ts"],
13 | seeds: ["src/database/seeds/*.ts"],
14 | factories: ["src/database/factories/*.ts"],
15 | synchronize: true,
16 | charset: "utf8mb4",
17 | namingStrategy: new CustomNamingStrategy_1.CustomNamingStrategy()
18 | };
19 |
--------------------------------------------------------------------------------
/server/src/app.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from "express";
2 | import path from "path";
3 | import cors from "cors";
4 | // import session from "express-session";
5 |
6 | /**
7 | * middlewares
8 | */
9 | import morganLogger from "morgan";
10 |
11 | const app = express();
12 |
13 | /**
14 | * routing-controllers에서 기본으로 적용되는 middlewares
15 | * 1. body-parser
16 | * 2. multer
17 | */
18 | app.use(
19 | cors({
20 | origin: true,
21 | credentials: true
22 | })
23 | );
24 | app.use(morganLogger("dev"));
25 | app.use(express.json({ limit: "50mb" }));
26 | app.use(express.static(path.resolve("src", "public")));
27 | // app.use(
28 | // session({
29 | // secret: "#%*_#$(_#$()*%dapalda####!#!@%#$##@#",
30 | // resave: false,
31 | // saveUninitialized: true
32 | // })
33 | // );
34 | // app.use(express.static(path.resolve(__dirname, 'public')));
35 |
36 | /**
37 | * View EJS 설정
38 | */
39 | app.set("views", path.join("views"));
40 | app.set("view engine", "ejs");
41 |
42 | app.get("/login", (req, res) => {
43 | res.render("login");
44 | });
45 |
46 | export default app;
47 |
--------------------------------------------------------------------------------
/server/src/config/firestore.ts:
--------------------------------------------------------------------------------
1 | const config = require("../../keys/palda_account.json");
2 | export default config;
3 |
--------------------------------------------------------------------------------
/server/src/config/key.ts:
--------------------------------------------------------------------------------
1 | const config = {
2 | googleKey: {
3 | clientId: process.env.GOOGLE_CLIENT_ID,
4 | clientSecret: process.env.GOOGLE_CLIENT_SECRET
5 | },
6 | kakaoKey: {
7 | APIKey: process.env.KAKAO_API_KEY
8 | },
9 | jwtSecret: process.env.JWT_KEY
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/server/src/config/objectStorage.ts:
--------------------------------------------------------------------------------
1 | export const devConfig = {
2 | Bucket: process.env.DEV_BUCKET
3 | }
4 |
5 | export const prodConfig = {
6 | Bucket: process.env.BUCKET
7 | }
8 |
9 | export default process.env.NODE_ENV === "development" ? devConfig : prodConfig
10 |
--------------------------------------------------------------------------------
/server/src/constants/category.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "패션",
4 | "color": "#feaa6e",
5 | "sub": [
6 | "여성패션",
7 | "남성패션",
8 | "유아패션",
9 | "이벤트 의류",
10 | "공용 의류",
11 | "악세서리",
12 | "신발"
13 | ],
14 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/fashion.png"
15 | },
16 | {
17 | "name": "디지털",
18 | "color": "#779ecb",
19 | "sub": [
20 | "컴퓨터 & 악세서리",
21 | "노트북 & 악세서리",
22 | "전자 & 가전",
23 | "카메라",
24 | "TV",
25 | "휴대폰",
26 | "헤드폰",
27 | "비디오 게임",
28 | "게임 타이틀"
29 | ],
30 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/digital.png"
31 | },
32 | {
33 | "name": "수집",
34 | "color": "#5cb85c",
35 | "sub": [
36 | "예술품",
37 | "수집품",
38 | "인형",
39 | "동전 & 지폐",
40 | "기념품",
41 | "우표",
42 | "공예품"
43 | ],
44 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/collection.png"
45 | },
46 | {
47 | "name": "생활",
48 | "color": "#ffcf01",
49 | "sub": [
50 | "홈 데코 & DIY",
51 | "공구",
52 | "침구 & 커튼",
53 | "청소용품",
54 | " 조명",
55 | "식기 & 주방용품",
56 | "반려동물용품"
57 | ],
58 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/life.png"
59 | },
60 | {
61 | "name": "스포츠",
62 | "color": "#ff3466",
63 | "sub": [
64 | "골프",
65 | "낚시",
66 | "캠핑 & 하이킹",
67 | "사이클링",
68 | "골프 의류 & 악세서리",
69 | "낚시 릴",
70 | "피트니스"
71 | ],
72 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/sports.png"
73 | },
74 | {
75 | "name": "취미",
76 | "color": "#5c5749",
77 | "sub": [
78 | "도서",
79 | "장난감",
80 | "인형",
81 | "프라모델",
82 | "피규어",
83 | "악기 & 음향장비",
84 | "조명 효과",
85 | "보드게임"
86 | ],
87 | "imageUrl": "https://kr.object.ncloudstorage.com/paldastorage/logo/hobby.png"
88 | }
89 | ]
90 |
--------------------------------------------------------------------------------
/server/src/constants/oauthAPIs.ts:
--------------------------------------------------------------------------------
1 | export const kakao = {
2 | checkTokenExpired: "https://kapi.kakao.com/v1/user/access_token_info",
3 | getToken: "https://kauth.kakao.com/oauth/token"
4 | };
5 |
6 | export const google = {
7 | checkTokenExpired: "https://www.googleapis.com/oauth2/v2/tokeninfo",
8 | getToken: "https://www.googleapis.com/oauth2/v4/token",
9 | getAccess: "https://oauth2.googleapis.com/token",
10 | getUserInfo: "https://oauth2.googleapis.com/tokeninfo"
11 | };
12 |
--------------------------------------------------------------------------------
/server/src/controllers/api/BidContorller.ts:
--------------------------------------------------------------------------------
1 | import { Request } from "express";
2 | import { BidsService } from "../../services/BidService";
3 | import {
4 | JsonController,
5 | Get,
6 | Param,
7 | Post,
8 | BodyParam,
9 | Req,
10 | Put,
11 | Body,
12 | OnUndefined
13 | } from "routing-controllers";
14 |
15 | @JsonController("/bids")
16 | export class BidController {
17 | constructor(private readonly bidService: BidsService) {}
18 |
19 | @Get()
20 | public find() {
21 | return this.bidService.find();
22 | }
23 |
24 | @Get("/:id")
25 | public findOne(@Param("id") bidId: string) {
26 | return this.bidService.findOne(parseInt(bidId));
27 | }
28 |
29 | @Post()
30 | public create(
31 | @Body() body: any,
32 | @BodyParam("bidDate") bidDate: string,
33 | @BodyParam("bidPrice") bidPrice: string,
34 | @BodyParam("productId") productId: string,
35 | @BodyParam("userId") userId: string
36 | ) {
37 | return this.bidService.create(
38 | bidDate,
39 | parseInt(bidPrice),
40 | parseInt(productId),
41 | parseInt(userId)
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/server/src/controllers/api/ItemController.ts:
--------------------------------------------------------------------------------
1 | import {
2 | JsonController,
3 | Put,
4 | BodyParam,
5 | Post,
6 | Get,
7 | Param,
8 | QueryParam,
9 | HeaderParam
10 | } from "routing-controllers";
11 | import { ItemService } from "../../services/ItemService";
12 |
13 | @JsonController("/items")
14 | export class ItemController {
15 | constructor(private readonly itemService: ItemService) {}
16 |
17 | @Get("/category/:code")
18 | public find(@Param("code") categoryCode: number) {
19 | return this.itemService.find(Number(categoryCode));
20 | }
21 |
22 | @Get("/hot")
23 | public findHot() {
24 | return this.itemService.findHot();
25 | }
26 |
27 | @Get("/deadline")
28 | public findDeadline() {
29 | return this.itemService.findAndOrder(5, {
30 | extensionDate: "ASC"
31 | });
32 | }
33 |
34 | @Get("/related/:code/:id")
35 | public findRelated(@Param("code") categoryCode: number, @Param("id") id: number) {
36 | return this.itemService.findRelated(Number(id), Number(categoryCode));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/src/controllers/api/LogController.ts:
--------------------------------------------------------------------------------
1 | import { JsonController, Post, BodyParam } from "routing-controllers";
2 | import { LogService } from "../../services/LogService";
3 |
4 | @JsonController("/log")
5 | export class LogController {
6 | constructor(private readonly logService: LogService) {}
7 |
8 | @Post("/filter")
9 | public tradeList(
10 | @BodyParam("userid") userid: number,
11 | @BodyParam("dayago") dayago: number,
12 | @BodyParam("isSale") isSale: boolean,
13 | @BodyParam("isBuy") isBuy: boolean,
14 | @BodyParam("page") page: number,
15 | @BodyParam("limit") limit: number
16 | ) {
17 | if (isBuy && isSale) {
18 | return this.logService.findAllLog(userid, dayago, page, limit);
19 | } else {
20 | if (isBuy) {
21 | return this.logService.findBuyLog(userid, dayago, page, limit);
22 | } else if (isSale) {
23 | return this.logService.findSellLog(userid, dayago, page, limit);
24 | } else {
25 | return this.logService.findFailLog(userid, dayago, page, limit);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/src/controllers/api/StaticController.ts:
--------------------------------------------------------------------------------
1 | import { JsonController, Get, Param } from "routing-controllers";
2 | import categoryJson from "../../constants/category.json";
3 |
4 | @JsonController("/statics")
5 | export class StaticController {
6 | @Get("/categories")
7 | public categories() {
8 | return categoryJson;
9 | }
10 |
11 | @Get("/categories/:code")
12 | public getCategoryName(@Param("code") code: number) {
13 | const mainIdx = Math.floor(code / 1000) - 1;
14 | const subIdx = (code % 1000) - 1;
15 | console.dir(mainIdx);
16 | console.dir(subIdx);
17 | console.dir(categoryJson[mainIdx]);
18 | return { title: categoryJson[mainIdx].sub[subIdx] };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/server/src/controllers/api/StorageController.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Post,
3 | HeaderParam,
4 | ForbiddenError,
5 | ContentType,
6 | OnUndefined,
7 | OnNull,
8 | JsonController,
9 | BodyParam,
10 | Delete
11 | } from "routing-controllers";
12 |
13 | import s3 from "../../services/S3Service";
14 | import { UserService } from "../../services/UserService";
15 |
16 | @JsonController("/storage")
17 | @ContentType("image/*")
18 | @OnUndefined(403)
19 | @OnNull(403)
20 | export class StoreageController {
21 | constructor(private readonly userService: UserService) {}
22 | @Post("/image")
23 | public async image(
24 | @HeaderParam("x-auth") uid: string,
25 | @HeaderParam("x-timestamp") timestamp: string,
26 | @BodyParam("uri") data: string
27 | ) {
28 | if (uid === undefined || timestamp === undefined) return new ForbiddenError();
29 |
30 | const result = s3.creatObject("image/", data);
31 | return result;
32 | }
33 |
34 | @Post("/profile")
35 | public async profile(
36 | @HeaderParam("x-auth") uid: string,
37 | @HeaderParam("x-timestamp") timestamp: string,
38 | @BodyParam("uri") data: string,
39 | @BodyParam("id") id: number
40 | ) {
41 | if (uid === undefined || timestamp === undefined) return new ForbiddenError();
42 |
43 | const result = await s3.creatObject("profile/", data);
44 | const user = this.userService.updateUserProfile(id, result);
45 | return user;
46 | }
47 |
48 | @Delete("/image")
49 | public async remove(@BodyParam("url") url: string) {
50 | const result = await s3.deleteObject(url);
51 | return result;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/server/src/custom/CustomNamingStrategy.ts:
--------------------------------------------------------------------------------
1 | import { NamingStrategyInterface, DefaultNamingStrategy } from 'typeorm';
2 | import { snakeCase } from '../util/StringUtils';
3 |
4 | export class CustomNamingStrategy extends DefaultNamingStrategy
5 | implements NamingStrategyInterface {
6 | tableName(targetName: string, userSpecifiedName: string): string {
7 | return userSpecifiedName ? userSpecifiedName : snakeCase(targetName);
8 | }
9 |
10 | columnName(
11 | propertyName: string,
12 | customName: string,
13 | embeddedPrefixes: string[]
14 | ): string {
15 | return snakeCase(
16 | embeddedPrefixes.concat(customName ? customName : propertyName).join('_')
17 | );
18 | }
19 |
20 | columnNameCustomized(customName: string): string {
21 | return customName;
22 | }
23 |
24 | relationName(propertyName: string): string {
25 | return snakeCase(propertyName);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/database/factories/BidFactory.ts:
--------------------------------------------------------------------------------
1 | import { Bids } from "./../../models/Bids";
2 | import * as Faker from "faker";
3 | import { define } from "typeorm-seeding";
4 | import * as Moment from "moment";
5 | import { extendMoment } from "moment-range";
6 |
7 | const moment = extendMoment(Moment);
8 |
9 | const randomBetween = (min: number, max: number) => {
10 | return Math.round(Math.random() * (max - min) + min);
11 | };
12 |
13 | define(Bids, (faker: typeof Faker, settings: any): Bids => {
14 | const bid = new Bids();
15 |
16 | bid.bidPrice = faker.random.number();
17 | bid.bidDate = settings.date;
18 |
19 | // bid.product
20 | // bid.user
21 |
22 | return bid;
23 | });
24 |
--------------------------------------------------------------------------------
/server/src/database/factories/ImageFactory.ts:
--------------------------------------------------------------------------------
1 | import { Images } from "./../../models/Images";
2 | import * as Faker from "faker";
3 | import { define } from "typeorm-seeding";
4 |
5 | const randomBetween = (min: number, max: number) => {
6 | return Math.round(Math.random() * (max - min) + min);
7 | };
8 |
9 | define(Images, (faker: typeof Faker, settings: any): Images => {
10 | const image = new Images();
11 | image.imageUrl = `https://picsum.photos/id/${settings.index}/500/500`;
12 | //image.product
13 | return image;
14 | });
15 |
--------------------------------------------------------------------------------
/server/src/database/factories/ProductFactory.ts:
--------------------------------------------------------------------------------
1 | import { Products } from "./../../models/Products";
2 | import * as Faker from "faker";
3 | import { define } from "typeorm-seeding";
4 | import moment from "moment";
5 | import * as Moment from "moment";
6 | import { extendMoment } from "moment-range";
7 | import "moment-timezone";
8 |
9 | moment.tz.setDefault("Asia/Seoul");
10 |
11 | const randomBetween = (min: number, max: number) => {
12 | return Math.round(Math.random() * (max - min) + min);
13 | };
14 |
15 | define(Products, (faker: typeof Faker, settings: any): Products => {
16 | const product = new Products();
17 |
18 | const buyerId = !!randomBetween(0, 1)
19 | ? randomBetween(1, settings.userCount)
20 | : false;
21 | const isAuction = !!randomBetween(0, 1);
22 |
23 | const basePrice = faker.random.number();
24 | const halfPrice = basePrice / 2;
25 | const minPrice = basePrice / 3;
26 |
27 | const hasExtension = randomBetween(0, 1);
28 | const registerDate = moment()
29 | .add(settings.index, "m")
30 | .format("YYYY-MM-DD HH:mm:ss");
31 | const auctionDeadline = moment(registerDate)
32 | .add(3, "m")
33 | .format("YYYY-MM-DD HH:mm:ss");
34 | //.add(randomBetween(1, 60), "d")
35 | // 연장이 유무 여부도 임시 데이터로 입력
36 | const extensionDate = moment(auctionDeadline)
37 | .add(1, "m")
38 | .format("YYYY-MM-DD HH:mm:ss");
39 | //.add(randomBetween(1, 10), "d")
40 | // 등록일과 경매종료일 중간값으로 설정
41 | const soldDate = extendMoment(Moment)
42 | .range(new Date(registerDate), new Date(auctionDeadline))
43 | .center()
44 | .format("YYYY-MM-DD HH:mm:ss");
45 |
46 | faker.locale = "ko"; // faker.locale = "en";
47 | product.title = faker.commerce.productName();
48 | product.contents = faker.lorem.paragraphs();
49 | product.immediatePrice = basePrice;
50 | product.isAuction = isAuction;
51 | product.auctionDeadline = auctionDeadline;
52 | product.extensionDate = extensionDate;
53 |
54 | if (isAuction) {
55 | product.hopePrice = halfPrice;
56 | product.startBidPrice = minPrice;
57 | }
58 |
59 | if (buyerId) {
60 | product.buyerId = buyerId;
61 | product.soldPrice = randomBetween(minPrice, basePrice);
62 | product.soldDate = soldDate;
63 | }
64 |
65 | product.registerDate = registerDate;
66 | product.thumbnailUrl = `https://picsum.photos/id/${settings.index}/400/400`;
67 | product.categoryCode = randomBetween(1, 7);
68 |
69 | //product.images =
70 | //product.seller =
71 | //product.bids =
72 |
73 | return product;
74 | });
75 |
--------------------------------------------------------------------------------
/server/src/database/factories/UserFactory.ts:
--------------------------------------------------------------------------------
1 | import { Users } from "../../models/Users";
2 | import * as Faker from "faker";
3 | import { define } from "typeorm-seeding";
4 |
5 | define(Users, (faker: typeof Faker, settings: any): Users => {
6 | faker.locale = "en";
7 |
8 | const user = new Users();
9 | user.loginId = faker.lorem.word();
10 | user.password = faker.random.number().toString();
11 | user.salt = faker.lorem.word();
12 | faker.locale = "ko";
13 | user.name = `${faker.name.lastName()}${faker.name.firstName()}`;
14 | faker.locale = "en";
15 | user.profileUrl = `https://i.pravatar.cc/150?img=${settings.index}`;
16 | user.mannerPoint = Math.round(Math.random() * (5 - 0) + 0);
17 | user.isDelete = false;
18 | user.email = faker.internet.email();
19 | user.accessToken = faker.lorem.word();
20 | user.refreshToken = faker.lorem.word();
21 |
22 | //user.products =
23 | //user.bids =
24 |
25 | return user;
26 | });
27 |
--------------------------------------------------------------------------------
/server/src/database/seeds/BidSeed.ts:
--------------------------------------------------------------------------------
1 | import { Products } from "./../../models/Products";
2 | import { Bids } from "./../../models/Bids";
3 | import { Factory, Seeder, times } from "typeorm-seeding";
4 | import { Connection } from "typeorm";
5 | import { Users } from "../../models/Users";
6 | import moment from "moment";
7 |
8 | const randomBetween = (min: number, max: number) => {
9 | return Math.round(Math.random() * (max - min) + min);
10 | };
11 |
12 | export class BidSeed implements Seeder {
13 | async run(factory: Factory, connection: Connection): Promise {
14 | const em = connection.createEntityManager();
15 |
16 | let productId = 1;
17 | const users = await em.find(Users);
18 |
19 | //모든 유저가 5개 제품에 대해서 각각 1개씩 입찰한 임시 데이터 넣기
20 | users.forEach(async user => {
21 | console.log(1);
22 | const products = await connection
23 | .getRepository(Products)
24 | .createQueryBuilder("product")
25 | .getMany();
26 |
27 | productId = productId + 5;
28 | console.log(2);
29 |
30 | const bids = await times(5, async productCount => {
31 | const product = products[productCount];
32 | const date = moment(product.auctionDeadline)
33 | .subtract(randomBetween(1, 10), "d")
34 | .format("YYYY-MM-DD HH:mm:ss");
35 |
36 | const bid = await factory(Bids)({ date }).seed();
37 | bid.user = user;
38 | bid.product = product;
39 | return await em.save(bid);
40 | });
41 |
42 | user.bids = bids;
43 | await em.save(user);
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/server/src/database/seeds/UserSeed.ts:
--------------------------------------------------------------------------------
1 | import { Images } from "./../../models/Images";
2 | import { Products } from "./../../models/Products";
3 | import { Factory, Seeder, times } from "typeorm-seeding";
4 | import { Connection } from "typeorm";
5 | import { Users } from "../../models/Users";
6 |
7 | export class UserSeed implements Seeder {
8 | async run(factory: Factory, connection: Connection): Promise {
9 | const em = connection.createEntityManager();
10 |
11 | let productId = 1;
12 | let imageId = 20 * 10 + 1; // userCount * productCount
13 | await times(20, async userCount => {
14 | const user = await factory(Users)({ index: userCount }).seed();
15 |
16 | const products = await times(10, async productCount => {
17 | const product = await factory(Products)({
18 | userCount: 20,
19 | index: productId++
20 | }).seed();
21 |
22 | const images = await times(5, async imageCount => {
23 | const image = factory(Images)({ index: imageId++ }).seed();
24 | return image;
25 | });
26 |
27 | product.images = images;
28 | return await em.save(product);
29 | });
30 |
31 | user.products = products;
32 | return await em.save(user);
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/server/src/dto/BidResponseDTO.ts:
--------------------------------------------------------------------------------
1 | import { UserResponseDTO } from "./UserResponseDTO";
2 | export class BidResponseDTO {
3 | id: number;
4 | bidPrice: number;
5 | bidDate: string;
6 | user: UserResponseDTO;
7 | }
8 |
--------------------------------------------------------------------------------
/server/src/dto/ImageDTO.ts:
--------------------------------------------------------------------------------
1 | import { Images } from "../models/Images"
2 | import { Products } from "../models/Products"
3 |
4 | export class ImageDTO {
5 | public create(productId: number, uri: string) {
6 | const image = new Images()
7 | const product = new Products()
8 | product.id = productId
9 |
10 | image.imageUrl = uri
11 | image.product = product
12 |
13 | return image
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/src/dto/ImageResponseDTO.ts:
--------------------------------------------------------------------------------
1 | export class ImageResponseDTO {
2 | id: number;
3 | imageUrl: string;
4 | }
5 |
--------------------------------------------------------------------------------
/server/src/dto/ProductCardResponseDTO.ts:
--------------------------------------------------------------------------------
1 | import { Products } from "../models/Products";
2 |
3 | export class ProductCardResponseDTO {
4 | constructor(product: Products, countBids: number, topBid: number) {
5 | this.id = product.id;
6 | this.title = product.title;
7 | this.contents = product.contents;
8 | this.immediatePrice = product.immediatePrice;
9 | this.hopePrice = product.hopePrice;
10 | this.startBidPrice = product.startBidPrice;
11 | this.registerDate = product.registerDate;
12 | this.auctionDeadline = product.auctionDeadline;
13 | this.extensionDate = product.extensionDate;
14 | this.soldPrice = product.soldPrice;
15 | this.soldDate = product.soldDate;
16 | this.thumbnailUrl = product.thumbnailUrl;
17 | this.categoryCode = product.categoryCode;
18 | this.isAuction = product.isAuction;
19 | this.buyerId = product.buyerId;
20 | this.countBids = countBids;
21 | this.topBid = topBid;
22 | }
23 | id: number;
24 | title: string;
25 | contents: string;
26 | immediatePrice: number;
27 | hopePrice: number;
28 | startBidPrice: number;
29 | registerDate: string;
30 | auctionDeadline: string;
31 | extensionDate: string;
32 | soldPrice: number;
33 | soldDate: string;
34 | thumbnailUrl: string;
35 | categoryCode: number;
36 | isAuction: boolean;
37 | buyerId: number;
38 | countBids: number;
39 | topBid: number;
40 | }
41 |
--------------------------------------------------------------------------------
/server/src/dto/ProductDTO.ts:
--------------------------------------------------------------------------------
1 | import { Products } from "../models/Products"
2 | import { Users } from "../models/Users"
3 |
4 | export class ProductsDTO {
5 | public create(
6 | userId: number,
7 | title: string,
8 | contents: string,
9 | immediatePrice: number,
10 | hopePrice: number,
11 | startBidPrice: number,
12 | registerDate: string,
13 | endDate: string,
14 | thumbnail: string,
15 | categoryCode: number,
16 | isAution: boolean
17 | ) {
18 | const products = new Products()
19 |
20 | // NotNull Field
21 | products.title = title
22 | products.contents = contents
23 | products.immediatePrice = immediatePrice
24 | products.registerDate = registerDate
25 | products.auctionDeadline = endDate
26 | products.thumbnailUrl = thumbnail
27 | products.categoryCode = categoryCode
28 | products.isAuction = isAution
29 |
30 | // Nullable Field
31 | products.hopePrice = hopePrice
32 | products.startBidPrice = startBidPrice
33 | products.extensionDate = endDate
34 |
35 | // 외래키
36 | // 물건을 등록하는 사람은 판매자가 된다.
37 | const user = new Users()
38 | user.id = userId
39 | products.seller = user
40 |
41 | return products
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/server/src/dto/ProductResponseDTO.ts:
--------------------------------------------------------------------------------
1 | import { UserResponseDTO } from "./UserResponseDTO";
2 | import { ImageResponseDTO } from "./ImageResponseDTO";
3 | import { BidResponseDTO } from "./BidResponseDTO";
4 |
5 | export class ProductResponseDTO {
6 | id: number;
7 | title: string;
8 | contents: string;
9 | immediatePrice: number;
10 | hopePrice: number;
11 | startBidPrice: number;
12 | registerDate: string;
13 | auctionDeadline: string;
14 | extensionDate: string;
15 | soldPrice: number;
16 | soldDate: string;
17 | thumbnailUrl: string;
18 | categoryCode: number;
19 | isAuction: boolean;
20 | buyerId: number;
21 | seller: UserResponseDTO;
22 | images: ImageResponseDTO[];
23 | bids: BidResponseDTO[];
24 | }
25 |
--------------------------------------------------------------------------------
/server/src/dto/UserDTO.ts:
--------------------------------------------------------------------------------
1 | import { Users } from "../models/Users";
2 |
3 | export class UserDTO {
4 | constructor(user: Users, isLogin: boolean, isSnsLogin: boolean) {
5 | this.id = user.id;
6 | this.loginId = user.loginId;
7 | this.name = user.name;
8 | this.profileUrl = user.profileUrl;
9 | this.mannerPoint = user.mannerPoint;
10 | this.email = user.email;
11 | this.accessToken = user.accessToken;
12 | this.refreshToken = user.refreshToken;
13 | this.isLogin = isLogin;
14 | this.isSnsLogin = isSnsLogin;
15 | }
16 | id: number;
17 | loginId: string;
18 | name: string;
19 | profileUrl: string;
20 | mannerPoint: number;
21 | email: string;
22 | accessToken: string;
23 | refreshToken: string;
24 | isLogin: Boolean;
25 | isSnsLogin: Boolean;
26 | }
27 |
--------------------------------------------------------------------------------
/server/src/dto/UserResponseDTO.ts:
--------------------------------------------------------------------------------
1 | export class UserResponseDTO {
2 | id: number;
3 | name: string;
4 | profileUrl: string;
5 | mannerPoint: number;
6 | email: string;
7 | }
8 |
--------------------------------------------------------------------------------
/server/src/middlewares/SystemLogger.ts:
--------------------------------------------------------------------------------
1 | import { ExpressMiddlewareInterface } from "routing-controllers";
2 | import { addLog } from "../services/FireStoreService";
3 |
4 | export class SystemLogger implements ExpressMiddlewareInterface {
5 | constructor() {}
6 |
7 | use(req: any, res: Response, next: () => any): void {
8 | const { id, name } = req.user;
9 | const url = req.url;
10 | const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
11 |
12 | addLog(id, name, ip, url);
13 |
14 | next();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/server/src/models/Bids.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
2 | import { Products } from "./Products";
3 | import { Users } from "./Users";
4 |
5 | @Entity()
6 | export class Bids {
7 | @PrimaryGeneratedColumn({ type: "bigint" })
8 | id: number;
9 |
10 | @Column()
11 | bidPrice: number;
12 |
13 | @Column({ type: "datetime" })
14 | bidDate: string;
15 |
16 | @ManyToOne(
17 | type => Products,
18 | product => product.bids
19 | )
20 | product: Products;
21 |
22 | @ManyToOne(
23 | type => Users,
24 | user => user.bids
25 | )
26 | user: Users;
27 | }
28 |
--------------------------------------------------------------------------------
/server/src/models/Images.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
2 | import { Products } from "./Products";
3 |
4 | @Entity()
5 | export class Images {
6 | @PrimaryGeneratedColumn()
7 | id: number;
8 |
9 | @Column()
10 | imageUrl: string;
11 |
12 | @ManyToOne(
13 | type => Products,
14 | product => product.images,
15 | { onDelete: "CASCADE", onUpdate: "NO ACTION" }
16 | )
17 | product: Products;
18 | }
19 |
--------------------------------------------------------------------------------
/server/src/models/Products.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne } from "typeorm";
2 | import { Images } from "./Images";
3 | import { Users } from "./Users";
4 | import { Bids } from "./Bids";
5 |
6 | @Entity()
7 | export class Products {
8 | @PrimaryGeneratedColumn()
9 | id: number;
10 |
11 | @Column()
12 | title: string;
13 |
14 | @Column({ type: "text" })
15 | contents: string;
16 |
17 | @Column()
18 | immediatePrice: number;
19 |
20 | @Column({ nullable: true })
21 | hopePrice: number;
22 |
23 | @Column({ nullable: true })
24 | startBidPrice: number;
25 |
26 | @Column({ type: "datetime", default: () => "CURRENT_TIMESTAMP" })
27 | registerDate: string;
28 |
29 | @Column({ type: "datetime", nullable: true })
30 | auctionDeadline: string;
31 |
32 | @Column({ type: "datetime", nullable: true })
33 | extensionDate: string;
34 |
35 | @Column({ nullable: true })
36 | soldPrice: number;
37 |
38 | @Column({ type: "datetime", nullable: true })
39 | soldDate: string;
40 |
41 | @Column()
42 | thumbnailUrl: string;
43 |
44 | @Column()
45 | categoryCode: number;
46 |
47 | @Column()
48 | isAuction: boolean;
49 |
50 | @Column({ default: false })
51 | isEnd: boolean;
52 |
53 | @Column({ default: false })
54 | sellerCheck: boolean;
55 |
56 | @Column({ default: false })
57 | buyerCheck: boolean;
58 |
59 | @OneToMany(
60 | type => Images,
61 | image => image.product
62 | )
63 | images: Images[];
64 |
65 | @Column({ nullable: true })
66 | buyerId: number;
67 |
68 | @ManyToOne(
69 | type => Users,
70 | user => user.products
71 | )
72 | seller: Users;
73 |
74 | @OneToMany(
75 | type => Bids,
76 | bid => bid.product
77 | )
78 | bids: Bids[];
79 | }
80 |
--------------------------------------------------------------------------------
/server/src/models/Users.ts:
--------------------------------------------------------------------------------
1 | import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
2 | import { Products } from "./Products";
3 | import { Bids } from "./Bids";
4 |
5 | @Entity()
6 | export class Users {
7 | @PrimaryGeneratedColumn()
8 | id: number;
9 |
10 | @Column()
11 | loginId: string;
12 |
13 | @Column()
14 | password: string;
15 |
16 | @Column()
17 | salt: string;
18 |
19 | @Column()
20 | name: string;
21 |
22 | @Column({ nullable: true, default: null })
23 | profileUrl: string;
24 |
25 | @Column({ default: 10 })
26 | mannerPoint: number;
27 |
28 | @Column({ default: false })
29 | isDelete: boolean;
30 |
31 | @Column({ nullable: true, default: null })
32 | email: string;
33 |
34 | @Column()
35 | accessToken: string;
36 |
37 | @Column()
38 | refreshToken: string;
39 |
40 | @OneToMany(
41 | type => Products,
42 | product => product.seller
43 | )
44 | products: Products[];
45 |
46 | @OneToMany(
47 | type => Bids,
48 | bid => bid.user
49 | )
50 | bids: Bids[];
51 | }
52 |
--------------------------------------------------------------------------------
/server/src/repositories/BidRepository.ts:
--------------------------------------------------------------------------------
1 | import { Bids } from "./../models/Bids";
2 | import { EntityRepository, EntityManager } from "typeorm";
3 |
4 | @EntityRepository()
5 | export class BidRepository {
6 | constructor(private readonly em: EntityManager) {}
7 |
8 | public find(start?: number, limit?: number) {
9 | return this.em
10 | .createQueryBuilder(Bids, "bids")
11 | .skip(start)
12 | .take(limit)
13 | .getMany();
14 | }
15 |
16 | public findHotItems() {
17 | return this.em
18 | .createQueryBuilder(Bids, "bids")
19 | .innerJoinAndSelect("bids.product", "products")
20 | .select("bids.product")
21 | .addSelect("MAX(bids.bid_price) as top_bid")
22 | .addSelect("COUNT(bids.product) as count")
23 | .groupBy("bids.product")
24 | .orderBy("count", "DESC")
25 | .addOrderBy("bids.product", "ASC")
26 | .getRawMany();
27 | }
28 |
29 | public findProductBidInfo(productId: number) {
30 | return this.em
31 | .createQueryBuilder(Bids, "bids")
32 | .innerJoinAndSelect("bids.product", "products")
33 | .where(`products.id= :id`, {
34 | id: productId
35 | })
36 | .select("bids.product")
37 | .addSelect("MAX(bids.bid_price) as top_bid")
38 | .addSelect("COUNT(bids.product) as count")
39 | .groupBy("bids.product")
40 | .getRawOne();
41 | }
42 |
43 | public findLastBidBy(productId: number) {
44 | return this.em.query(
45 | "SELECT * FROM bids WHERE product_id=? ORDER BY bid_date DESC LIMIT 1",
46 | [productId]
47 | );
48 | }
49 |
50 | public findOne(bidId: number) {
51 | return this.em.findOne(Bids, bidId);
52 | }
53 |
54 | public findByProductId(productId: number) {
55 | return this.em
56 | .createQueryBuilder(Bids, "bids")
57 | .innerJoinAndSelect("bids.product", "product")
58 | .where(`product.id= :id`, {
59 | id: productId
60 | })
61 | .getMany();
62 | }
63 |
64 | public create(bid: Bids) {
65 | return this.em.save(bid);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/server/src/repositories/ImageRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, EntityManager } from "typeorm"
2 | import { ImageDTO } from "../dto/ImageDTO"
3 |
4 | @EntityRepository()
5 | export class ImageRepository {
6 | constructor(private readonly em: EntityManager) {}
7 |
8 | /* PUT */
9 | public async create(productId: number, images: string[]) {
10 | const dto = new ImageDTO()
11 |
12 | const entities = images.map(value => dto.create(productId, value))
13 |
14 | return await this.em.save(entities)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/server/src/repositories/LogRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, EntityManager, MoreThanOrEqual, IsNull, Between } from "typeorm";
2 | import { prevDay, Today } from "../util/DateUtils";
3 | import { Products } from "../models/Products";
4 |
5 | @EntityRepository()
6 | export class LogRepository {
7 | constructor(private readonly em: EntityManager) {}
8 |
9 | public findBuy(userid: number, dayago: number, page: number, limit: number) {
10 | return this.em.findAndCount(Products, {
11 | relations: ["seller"],
12 | where: {
13 | soldDate: MoreThanOrEqual(prevDay(dayago)),
14 | buyerId: userid
15 | },
16 | order: {
17 | soldDate: "DESC"
18 | },
19 | skip: (page - 1) * limit,
20 | take: limit,
21 | cache: true
22 | });
23 | }
24 |
25 | public findSell(userid: number, dayago: number, page: number, limit: number) {
26 | return this.em.findAndCount(Products, {
27 | relations: ["seller"],
28 | where: {
29 | soldDate: MoreThanOrEqual(prevDay(dayago)),
30 | seller: { id: userid }
31 | },
32 | order: {
33 | soldDate: "DESC"
34 | },
35 | skip: (page - 1) * limit,
36 | take: limit,
37 | cache: true
38 | });
39 | }
40 |
41 | public findFail(userid: number, dayago: number, page: number, limit: number) {
42 | return this.em.findAndCount(Products, {
43 | relations: ["seller"],
44 | where: {
45 | soldDate: IsNull(),
46 | extensionDate: Between(prevDay(dayago), Today()),
47 | seller: { id: userid }
48 | },
49 | order: {
50 | registerDate: "DESC"
51 | },
52 | skip: (page - 1) * limit,
53 | take: limit,
54 | cache: true
55 | });
56 | }
57 |
58 | public findAll(userid: number, dayago: number, page: number, limit: number) {
59 | return this.em.findAndCount(Products, {
60 | relations: ["seller"],
61 | where: [
62 | {
63 | soldDate: MoreThanOrEqual(prevDay(dayago)),
64 | seller: { id: userid }
65 | },
66 | {
67 | soldDate: MoreThanOrEqual(prevDay(dayago)),
68 | buyerId: userid
69 | }
70 | ],
71 | order: {
72 | soldDate: "DESC"
73 | },
74 | skip: (page - 1) * limit,
75 | take: limit,
76 | cache: true
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/server/src/repositories/UserRepository.ts:
--------------------------------------------------------------------------------
1 | import { EntityRepository, EntityManager } from "typeorm";
2 | import { Users } from "../models/Users";
3 | /** Entity Manager - Constructor Injecction
4 | * 1. TypeOrm 역시 TypeDI의 Container를 사용한다.(server.ts 참조)
5 | * 2. 그래서 TypeOrm의 EntityManager는 TypeDI에 등록되있다.
6 | * 3. 그렇기 때문에, 아래서 @Inject 데코레이터 없이 생성자 주입이 가능하다.
7 | * 4. TODO: TypeDi와 Container 연결없이, 생성자 Injection이 되는지 확인해보자.
8 | */
9 |
10 | @EntityRepository()
11 | export class UserRepository {
12 | constructor(private readonly em: EntityManager) {}
13 |
14 | public find() {
15 | return this.em.find(Users);
16 | }
17 |
18 | public findOne(loginId: string) {
19 | return this.em.findOne(Users, {
20 | where: { loginId }
21 | });
22 | }
23 | public findOnebyIdx(id: number) {
24 | return this.em.findOne(Users, {
25 | where: { id }
26 | });
27 | }
28 |
29 | public findOneByToken(accessToken: string) {
30 | return this.em.findOne(Users, {
31 | where: { accessToken }
32 | });
33 | }
34 |
35 | public save(user: Users) {
36 | return this.em.save(user);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/src/server.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config";
2 | import "reflect-metadata";
3 |
4 | /**
5 | * IOC Container, DI based on Decorator, ORM
6 | */
7 | import { Container } from "typedi";
8 | import {
9 | useExpressServer,
10 | useContainer as routingUseContainer,
11 | Action
12 | } from "routing-controllers";
13 | import {
14 | getRepository,
15 | createConnection,
16 | useContainer as ormUseContainer
17 | } from "typeorm";
18 |
19 |
20 | /**
21 | * Custom Setting Import
22 | */
23 | import { CustomNamingStrategy } from "./custom/CustomNamingStrategy";
24 |
25 | /**
26 | * Load Apps(web, database etc)
27 | */
28 | import app from "./app";
29 | import { Users } from "./models/Users";
30 |
31 | routingUseContainer(Container);
32 | ormUseContainer(Container);
33 | createConnection({
34 | type: "mysql",
35 | host:
36 | process.env.NODE_ENV === "development"
37 | ? process.env.DB_DOCKER_COMPOSE_SERVICE_HOST
38 | : process.env.DB_HOST,
39 | port: Number(process.env.DB_PORT),
40 | username: process.env.DB_USER,
41 | password: process.env.DB_PASSWORD,
42 | database: process.env.DB_NAME,
43 | logging: true,
44 | entities: [__dirname + "/models/*.js"],
45 | synchronize: process.env.NODE_ENV === "development" ? true : false,
46 | charset: "utf8mb4",
47 | namingStrategy: new CustomNamingStrategy()
48 | })
49 | .then()
50 | .catch(err => console.log(err));
51 |
52 | const expressApp = useExpressServer(app, {
53 | routePrefix: "api",
54 | controllers: [__dirname + "/controllers/**/*.js"],
55 | middlewares: [__dirname + "/middlewares/**/*.js"],
56 | authorizationChecker: async (action: Action) => {
57 | const token = action.request.headers["access-token"];
58 | const user = await getRepository(Users).findOne({
59 | where: { accessToken: token }
60 | });
61 | if (user === undefined) return false;
62 | return !!(action.request.user = {
63 | id: (user).id,
64 | name: (user).name
65 | });
66 | }
67 | // interceptors: [__dirname + "/interceptors/**/*.js"]
68 | });
69 |
70 | expressApp.listen(3000, () => {
71 | console.log("=====Express Server Started=====");
72 | console.log("=====Process Env=====");
73 | console.dir(process.env.NODE_ENV);
74 | console.dir(process.env);
75 | });
76 |
--------------------------------------------------------------------------------
/server/src/services/FireStoreService.ts:
--------------------------------------------------------------------------------
1 | import * as admin from "firebase-admin";
2 | import serviceAccount from "../config/firestore";
3 | import { getDateStr, Today } from "../util/DateUtils";
4 |
5 | admin.initializeApp({
6 | credential: admin.credential.cert(serviceAccount),
7 | databaseURL: process.env.FIRESTORE_BASE_URL
8 | });
9 |
10 | export const addLog = (id: number, name: string, ip: string, path: string) => {
11 | const db = admin.firestore();
12 | const timestamp = Date.now().toString();
13 |
14 | db.collection(getDateStr(new Date()))
15 | .doc(timestamp)
16 | .set({
17 | id,
18 | name,
19 | ip,
20 | path,
21 | date: Today()
22 | });
23 | };
24 |
25 | export const readLog = (date: string) => {
26 | const db = admin.firestore();
27 | return db.collection(date).get();
28 | };
29 |
--------------------------------------------------------------------------------
/server/src/services/LogService.ts:
--------------------------------------------------------------------------------
1 | import { Service } from "typedi";
2 | import { LogRepository } from "../repositories/LogRepository";
3 | import { InjectRepository } from "typeorm-typedi-extensions";
4 |
5 | /** TODO: Transaction을 어떻게 처리해야 좋을까? */
6 | @Service()
7 | export class LogService {
8 | constructor(@InjectRepository() private readonly LogRepository: LogRepository) {}
9 |
10 | public findBuyLog(userid: number, dayago: number, page: number, limit: number) {
11 | return this.LogRepository.findBuy(userid, dayago, page, limit);
12 | }
13 |
14 | public findSellLog(userid: number, dayago: number, page: number, limit: number) {
15 | return this.LogRepository.findSell(userid, dayago, page, limit);
16 | }
17 |
18 | public findAllLog(userid: number, dayago: number, page: number, limit: number) {
19 | return this.LogRepository.findAll(userid, dayago, page, limit);
20 | }
21 | public findFailLog(userid: number, dayago: number, page: number, limit: number) {
22 | return this.LogRepository.findFail(userid, dayago, page, limit);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/server/src/services/S3Service.ts:
--------------------------------------------------------------------------------
1 | import AWS from "aws-sdk";
2 | import { randomFileName } from "../util/StringUtils";
3 | import objectStorage from "../config/objectStorage";
4 |
5 | const { END_POINT, REGION, ACCESS_KEY, SECRET_KEY } = process.env;
6 | const { Bucket } = objectStorage;
7 |
8 | AWS.config.update({
9 | accessKeyId: ACCESS_KEY,
10 | secretAccessKey: SECRET_KEY
11 | });
12 |
13 | const S3 = new AWS.S3({
14 | endpoint: END_POINT,
15 | region: REGION
16 | });
17 |
18 | const creatObject = async (path: string, data: string) => {
19 | const rawData = new Buffer(data, "base64");
20 | const Key = `${path}${randomFileName()}`;
21 |
22 | const result = await S3.putObject({
23 | Bucket: String(Bucket),
24 | Key,
25 | ACL: "public-read",
26 | Body: rawData
27 | }).promise();
28 |
29 | return `${END_POINT}/${Bucket}/${Key}`;
30 | };
31 |
32 | const deleteObject = async (url: string) => {
33 | const getFile = url.replace(String(END_POINT) + "/", "");
34 |
35 | return await S3.deleteObject({
36 | Bucket: String(Bucket),
37 | Key: getFile
38 | }).promise();
39 | };
40 |
41 | export default { creatObject, deleteObject };
42 |
--------------------------------------------------------------------------------
/server/src/util/DateUtils.ts:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import "moment-timezone";
3 |
4 | moment.tz.setDefault("Asia/Seoul");
5 | /**
6 | * 오늘의 날짜
7 | */
8 | export const Today = () => moment().format("YYYY-MM-DD HH:mm:ss");
9 |
10 | /**
11 | * Date객체를 yyyy-mm-dd 형식으로 변환해줌
12 | *
13 | * @param myDate
14 | */
15 | export function getDateStr(myDate: Date) {
16 | var year = myDate.getFullYear();
17 | var month = ("0" + (myDate.getMonth() + 1)).slice(-2);
18 | var day = ("0" + myDate.getDate()).slice(-2);
19 | return year + "-" + month + "-" + day;
20 | }
21 |
22 | /**
23 | * 몇일전 날짜를 구할때 사용
24 | *
25 | * @param days
26 | */
27 | export function prevDay(days: number) {
28 | var d = new Date();
29 | var dayOfMonth = d.getDate();
30 | d.setDate(dayOfMonth - days);
31 | return getDateStr(d);
32 | }
33 |
--------------------------------------------------------------------------------
/server/src/util/StringUtils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts string into camelCase.
3 | *
4 | * @see http://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
5 | */
6 | export function camelCase(str: string, firstCapital: boolean = false): string {
7 | return str.replace(/^([A-Z])|[\s-_](\w)/g, function(match, p1, p2, offset) {
8 | if (firstCapital === true && offset === 0) return p1;
9 | if (p2) return p2.toUpperCase();
10 | return p1.toLowerCase();
11 | });
12 | }
13 |
14 | /**
15 | * Converts string into snake-case.
16 | *
17 | * @see https://regex101.com/r/QeSm2I/1
18 | */
19 | export function snakeCase(str: string) {
20 | return str
21 | .replace(/(?:([a-z])([A-Z]))|(?:((?!^)[A-Z])([a-z]))/g, "$1_$3$2$4")
22 | .toLowerCase();
23 | }
24 |
25 | /**
26 | * Converts string into Title Case.
27 | *
28 | * @see http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript
29 | */
30 | export function titleCase(str: string): string {
31 | return str.replace(
32 | /\w\S*/g,
33 | txt => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
34 | );
35 | }
36 |
37 | export function random5String() {
38 | return Math.random()
39 | .toString(36)
40 | .substr(2, 5);
41 | }
42 |
43 | export function randomFileName() {
44 | const dateTime = Date.now();
45 |
46 | return `${random5String()}${random5String()}${random5String()}${random5String()}${dateTime}`;
47 | }
48 |
49 | export function keyValue2Str(obj: any) {
50 | const arr = [];
51 | for (let key in obj) {
52 | if (obj.hasOwnProperty(key)) {
53 | arr.push(key + "=" + obj[key]);
54 | }
55 | }
56 | return arr.join("&");
57 | }
58 |
--------------------------------------------------------------------------------
/server/src/util/authUtils.ts:
--------------------------------------------------------------------------------
1 | import crypto from "crypto";
2 | import jwt from "jsonwebtoken";
3 | import config from "../config/key";
4 |
5 | const { jwtSecret } = config;
6 |
7 | export function encryptPassword(password: string) {
8 | const salt = crypto.randomBytes(30).toString("base64");
9 | const key = crypto.pbkdf2Sync(password, salt, 102935, 30, "sha512");
10 | return { salt, result: key.toString("base64") };
11 | }
12 |
13 | export function checkPassword(
14 | inputPwd: string,
15 | encryptedPwd: string,
16 | salt: string
17 | ) {
18 | const key = crypto.pbkdf2Sync(inputPwd, salt, 102935, 30, "sha512");
19 | return key.toString("base64") === encryptedPwd;
20 | }
21 |
22 | export const checkIsSnsLogin = (token: string) =>
23 | token.includes("kakao_") || token.includes("google_") ? true : false;
24 |
25 | export const makeTokens = (loginId: string) => {
26 | const accessToken = jwt.sign({ loginId }, `${jwtSecret}`, {
27 | expiresIn: "2h"
28 | });
29 |
30 | const refreshToken = jwt.sign({ loginId }, `${jwtSecret}`, {
31 | expiresIn: "3 days"
32 | });
33 |
34 | return { accessToken, refreshToken };
35 | };
36 |
37 | export const replaceOAuthToken = (
38 | oauth: string,
39 | accessToken: string,
40 | refreshToken: string
41 | ) => {
42 | const at = accessToken.replace(`${oauth}_`, "");
43 | const rt = refreshToken.replace(`${oauth}_`, "");
44 | return { at, rt };
45 | };
46 |
--------------------------------------------------------------------------------
/server/src/util/fetchUtil.ts:
--------------------------------------------------------------------------------
1 | import fetch from "node-fetch";
2 |
3 | export const fetchToJsonWithCallback = (
4 | url: any,
5 | option: any,
6 | callback: Function
7 | ) => {
8 | fetch(url, option)
9 | .then(res => {
10 | return res.json();
11 | })
12 | .then(json => {
13 | if (callback !== undefined) callback(json);
14 | });
15 | };
16 |
17 | export const fetchToJson = async (url: any, option: any) => {
18 | return await fetch(url, option).then(res => {
19 | return res.json();
20 | });
21 | };
22 |
23 | export const option = {
24 | get: { method: "GET", headers: { "User-Agent": "Mozilla/5.0" } },
25 | post: {
26 | method: "POST",
27 | headers: { "User-Agent": "Mozilla/5.0" }
28 | },
29 | put: { method: "PUT", headers: { "User-Agent": "Mozilla/5.0" } },
30 | postJson: {
31 | method: "POST",
32 | headers: { "User-Agent": "Mozilla/5.0", "Content-Type": "application/json" }
33 | },
34 | putJson: {
35 | method: "PUT",
36 | headers: { "User-Agent": "Mozilla/5.0", "Content-Type": "application/json" }
37 | }
38 | };
39 |
40 | export default { fetchToJsonWithCallback, fetchToJson, option };
41 |
--------------------------------------------------------------------------------