From 7118f1dde4b54dfee39a48ff91aec62d34c24673 Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Sun, 27 Nov 2022 14:52:32 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20#68=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC?= =?UTF-8?q?=EC=8B=B1=20+=20react-slick=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package-lock.json | 104 ++++++++++++++++++ client/package.json | 2 + client/public/index.html | 4 + client/src/App.tsx | 2 + .../Card/RecruitTextCard/RecruitTextCard.tsx | 32 ++++++ client/src/components/Header/Header.tsx | 7 +- client/src/pages/MainPage.tsx | 4 + client/src/pages/MainPage/MainPage.data.ts | 43 ++++++++ client/src/pages/MainPage/MainPage.styles.ts | 39 +++++++ client/src/pages/MainPage/MainPage.tsx | 53 +++++++++ client/src/styles/color.ts | 1 + 11 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 client/src/components/Card/RecruitTextCard/RecruitTextCard.tsx create mode 100644 client/src/pages/MainPage/MainPage.data.ts create mode 100644 client/src/pages/MainPage/MainPage.styles.ts create mode 100644 client/src/pages/MainPage/MainPage.tsx diff --git a/client/package-lock.json b/client/package-lock.json index caa300a..e452a2d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -23,6 +23,7 @@ "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^6.4.3", "react-scripts": "5.0.1", + "react-slick": "^0.29.0", "recoil": "^0.7.6", "styled-components": "^5.3.6", "web-vitals": "^2.1.4" @@ -39,6 +40,7 @@ "@storybook/preset-create-react-app": "^4.1.2", "@storybook/react": "^6.5.13", "@storybook/testing-library": "^0.0.13", + "@types/react-slick": "^0.23.10", "@types/styled-components": "^5.1.26", "@typescript-eslint/eslint-plugin": "^5.42.1", "babel-plugin-named-exports-order": "^0.0.2", @@ -11292,6 +11294,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-slick": { + "version": "0.23.10", + "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.10.tgz", + "integrity": "sha512-ZiqdencANDZy6sWOWJ54LDvebuXFEhDlHtXU9FFipQR2BcYU2QJxZhvJPW6YK7cocibUiNn+YvDTbt1HtCIBVA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/recoil": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/recoil/-/recoil-0.0.9.tgz", @@ -14072,6 +14083,11 @@ "node": ">=0.10.0" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -16242,6 +16258,11 @@ "node": ">=10.13.0" } }, + "node_modules/enquire.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz", + "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==" + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -22389,6 +22410,14 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "dependencies": { + "string-convert": "^0.2.0" + } + }, "node_modules/json5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", @@ -26734,6 +26763,22 @@ } } }, + "node_modules/react-slick": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.29.0.tgz", + "integrity": "sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA==", + "dependencies": { + "classnames": "^2.2.5", + "enquire.js": "^2.1.6", + "json2mq": "^0.2.0", + "lodash.debounce": "^4.0.8", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -27295,6 +27340,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -29004,6 +29054,11 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -40511,6 +40566,15 @@ "@types/react": "*" } }, + "@types/react-slick": { + "version": "0.23.10", + "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.10.tgz", + "integrity": "sha512-ZiqdencANDZy6sWOWJ54LDvebuXFEhDlHtXU9FFipQR2BcYU2QJxZhvJPW6YK7cocibUiNn+YvDTbt1HtCIBVA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/recoil": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/recoil/-/recoil-0.0.9.tgz", @@ -42702,6 +42766,11 @@ } } }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz", @@ -44381,6 +44450,11 @@ "tapable": "^2.2.0" } }, + "enquire.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz", + "integrity": "sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw==" + }, "entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -48914,6 +48988,14 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, + "json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "requires": { + "string-convert": "^0.2.0" + } + }, "json5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", @@ -52028,6 +52110,18 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-slick": { + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.29.0.tgz", + "integrity": "sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA==", + "requires": { + "classnames": "^2.2.5", + "enquire.js": "^2.1.6", + "json2mq": "^0.2.0", + "lodash.debounce": "^4.0.8", + "resize-observer-polyfill": "^1.5.0" + } + }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -52453,6 +52547,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -53819,6 +53918,11 @@ "safe-buffer": "~5.2.0" } }, + "string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", diff --git a/client/package.json b/client/package.json index 306d73a..efc5999 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ "react-infinite-scroll-component": "^6.1.0", "react-router-dom": "^6.4.3", "react-scripts": "5.0.1", + "react-slick": "^0.29.0", "recoil": "^0.7.6", "styled-components": "^5.3.6", "web-vitals": "^2.1.4" @@ -70,6 +71,7 @@ "@storybook/preset-create-react-app": "^4.1.2", "@storybook/react": "^6.5.13", "@storybook/testing-library": "^0.0.13", + "@types/react-slick": "^0.23.10", "@types/styled-components": "^5.1.26", "@typescript-eslint/eslint-plugin": "^5.42.1", "babel-plugin-named-exports-order": "^0.0.2", diff --git a/client/public/index.html b/client/public/index.html index 42bea02..28bd3e5 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -9,6 +9,10 @@ + + React App diff --git a/client/src/App.tsx b/client/src/App.tsx index 1b4797c..e712f32 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useState } from "react"; import SignUp from "#pages/SignUp"; import Login from "#pages/Login"; import MainPage from "#pages/MainPage"; +import MainPage2 from "#pages/MainPage/MainPage"; import Courses from "#pages/Courses"; import { useRecoilState } from "recoil"; import { userState } from "#atoms/userState"; @@ -46,6 +47,7 @@ function App() { return ( } /> + } /> } /> } /> } /> diff --git a/client/src/components/Card/RecruitTextCard/RecruitTextCard.tsx b/client/src/components/Card/RecruitTextCard/RecruitTextCard.tsx new file mode 100644 index 0000000..9e228af --- /dev/null +++ b/client/src/components/Card/RecruitTextCard/RecruitTextCard.tsx @@ -0,0 +1,32 @@ +import DetailLabel from "#components/DetailLabel/DetailLabel"; +import { Recruit } from "#types/Recruit"; +import { getDisplayPaceString } from "#utils/stringtils"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; + +const Card = styled.div` + padding: 12px 16px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25); + border-radius: 8px; + div:not(:last-child) { + margin-bottom: 30px; + } +`; + +interface RecruitTextCardProps { + data: Recruit; +} + +const RecruitTextCard = ({ data }: RecruitTextCardProps) => { + const navigate = useNavigate(); + return ( + navigate(`/recruit/${data.id}`)}> + + + + + + + ); +}; +export default RecruitTextCard; diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index 5873373..65a09fd 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -14,7 +14,6 @@ const HeaderWrapper = styled.div` img { width: 24px; height: 24px; - cursor: pointer; } div { width: 24px; @@ -25,13 +24,15 @@ const HeaderWrapper = styled.div` interface HeaderProps { loggedIn?: boolean; text: string; + isMain?: boolean; } -const Header = ({ loggedIn, text }: HeaderProps) => { +const Header = ({ loggedIn, text, isMain = false }: HeaderProps) => { const navigate = useNavigate(); return ( - navigate(-1)} /> + {isMain ?
: navigate(-1)} />} +

{text}

{loggedIn ? navigate("/mypage")} /> :
} diff --git a/client/src/pages/MainPage.tsx b/client/src/pages/MainPage.tsx index 67ca897..d3874d7 100644 --- a/client/src/pages/MainPage.tsx +++ b/client/src/pages/MainPage.tsx @@ -27,6 +27,9 @@ const MainPage = () => { const handleRecruitsClick = () => { navigate("/recruits"); }; + const handleMainPage = () => { + navigate("/main"); + }; return ( <> @@ -37,6 +40,7 @@ const MainPage = () => { + ); }; diff --git a/client/src/pages/MainPage/MainPage.data.ts b/client/src/pages/MainPage/MainPage.data.ts new file mode 100644 index 0000000..ed78e47 --- /dev/null +++ b/client/src/pages/MainPage/MainPage.data.ts @@ -0,0 +1,43 @@ +import { Course } from "#types/Course"; +import { Recruit } from "#types/Recruit"; + +export const course: Course = { + id: 1, + title: "Dirty Ho (Lan tou He)", + img: "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png", + path: JSON.stringify([ + { lat: 126.57091836134346, lng: 33.45090000378721 }, + { lat: 126.57004847387998, lng: 33.450635526049844 }, + { lat: 126.56931524544794, lng: 33.45101165404891 }, + { lat: 126.56932224193068, lng: 33.44959616387136 }, + { lat: 126.5700747443057, lng: 33.449670903389 }, + { lat: 126.570502727405, lng: 33.450123187413496 }, + ]), + pathLength: 60, + hDong: { + name: "", + }, + createdAt: "2022-11-21T08:55:33.162Z", +}; + +export const recruit: Recruit = { + id: 125, + title: "gustas", + startTime: "2022-11-25T14:50:00.000Z", + maxPpl: 4, + currentPpl: 1, + userId: "guss95", + createdAt: "2022-11-24T15:04:46.095Z", + pace: 330, + course: { + id: 1, + title: "Dirty Ho (Lan tou He)", + img: "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png", + path: "", + pathLength: 60, + hDong: { + name: "잠실동", + }, + createdAt: "2022-11-21T08:55:33.162Z", + }, +}; diff --git a/client/src/pages/MainPage/MainPage.styles.ts b/client/src/pages/MainPage/MainPage.styles.ts new file mode 100644 index 0000000..b6ab782 --- /dev/null +++ b/client/src/pages/MainPage/MainPage.styles.ts @@ -0,0 +1,39 @@ +import styled from "styled-components"; +import { COLOR } from "styles/color"; +import { flexRowSpaceBetween } from "styles/flex"; +import { fontXLarge } from "styles/font"; + +export const MainPageContainer = styled.div` + padding: 15px 0px; + > div:nth-child(1) { + margin-bottom: 40px; + } +`; + +export const CarouselWrapper = styled.div` + .slick-slide { + padding: 5px; + } + .slick-dots { + margin-top: 30px; + .slick-active { + button { + ::before { + color: ${COLOR.ORANGE} !important; + } + } + } + li { + margin: 0 1px; + } + } +`; + +export const TitleWrapper = styled.div` + ${flexRowSpaceBetween}; + padding: 0px 10px; +`; + +export const ListTitle = styled.span` + ${fontXLarge(COLOR.BLACK, 500)}; +`; diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx new file mode 100644 index 0000000..4eb8cf9 --- /dev/null +++ b/client/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,53 @@ +import Header from "#components/Header/Header"; +import MoreButton from "#components/MoreButton/MoreButton"; +import Slider, { Settings } from "react-slick"; +import CourseCard from "#components/Card/CourseCard/CourseCard"; +import { CarouselWrapper, ListTitle, MainPageContainer, TitleWrapper } from "./MainPage.styles"; +import RecruitTextCard from "#components/Card/RecruitTextCard/RecruitTextCard"; +import { course, recruit } from "./MainPage.data"; + +const settings: Settings = { + centerMode: true, + infinite: true, + slidesToShow: 1, + dots: true, + slidesToScroll: 1, +}; + +const MainPage = () => { + return ( + <> +
+ +
+ + 코스 목록 + + + + + {new Array(10).fill(course).map((c, idx) => ( + + ))} + + +
+
+ + 모집 목록 + + + + + {new Array(10).fill(recruit).map((r, idx) => ( + + ))} + + +
+
+ + ); +}; + +export default MainPage; diff --git a/client/src/styles/color.ts b/client/src/styles/color.ts index 9b885ae..7f7ebf5 100644 --- a/client/src/styles/color.ts +++ b/client/src/styles/color.ts @@ -5,4 +5,5 @@ export enum COLOR { DARK_GRAY = "#757D87", LIGHT_GRAY = "#808080", BABY_BLUE = "#ACB7C7", + ORANGE = "#FFB800", } From ca738d46cadfeaa5ae0212bd6a92e50d2a1257e8 Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Wed, 30 Nov 2022 13:20:33 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20#68=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC?= =?UTF-8?q?=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/{MainPage => Main2}/MainPage.data.ts | 0 client/src/pages/{MainPage => Main2}/MainPage.styles.ts | 0 client/src/pages/{MainPage => Main2}/MainPage.tsx | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename client/src/pages/{MainPage => Main2}/MainPage.data.ts (100%) rename client/src/pages/{MainPage => Main2}/MainPage.styles.ts (100%) rename client/src/pages/{MainPage => Main2}/MainPage.tsx (100%) diff --git a/client/src/pages/MainPage/MainPage.data.ts b/client/src/pages/Main2/MainPage.data.ts similarity index 100% rename from client/src/pages/MainPage/MainPage.data.ts rename to client/src/pages/Main2/MainPage.data.ts diff --git a/client/src/pages/MainPage/MainPage.styles.ts b/client/src/pages/Main2/MainPage.styles.ts similarity index 100% rename from client/src/pages/MainPage/MainPage.styles.ts rename to client/src/pages/Main2/MainPage.styles.ts diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/Main2/MainPage.tsx similarity index 100% rename from client/src/pages/MainPage/MainPage.tsx rename to client/src/pages/Main2/MainPage.tsx From 6dc0036ccef9eb4d707a7953c5f2c80605baba16 Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Wed, 30 Nov 2022 13:26:02 +0900 Subject: [PATCH 3/7] resolve conflict --- .github/workflows/develop-check.yml | 104 +- .gitignore | 2 + client/.storybook/main.js | 1 + client/package-lock.json | 15 + client/package.json | 1 + client/src/App.tsx | 22 +- .../components/AddressList/AddressList.tsx | 14 +- .../src/components/Button/Button.stories.tsx | 3 +- client/src/components/Card/Card.styles.ts | 1 - .../DetailLabel/DetailLabel.stories.tsx | 2 +- .../FilterBar/FilterBar.stories.tsx | 2 +- .../src/components/Header/Header.stories.tsx | 9 +- client/src/components/Header/Header.tsx | 8 +- .../AddressSearchInput/AddressSearchInput.tsx | 61 + .../AddressSearchInput/Input.stories.tsx | 13 + client/src/components/Input/Input.stories.tsx | 2 +- client/src/components/Input/Input.tsx | 16 +- .../Input/MaxPplInput/MaxPplInput.tsx | 30 + .../Input/PaceInput/PaceInput.stories.tsx | 2 +- .../StartTimeInput/StartTimeInput.stories.tsx | 11 + .../Input/StartTimeInput/StartTimeInput.tsx | 32 + .../MapControl/MapControl.styles.ts | 4 +- .../PlaceSearch/PlaceSearch.stories.tsx | 16 + .../PlaceSearch/PlaceSearch.styles.ts | 4 +- client/src/components/Modal/Modal.stories.tsx | 6 +- client/src/components/Modal/Modal.styles.ts | 3 +- .../MoreButton/MoreButton.stories.tsx | 2 +- .../OnOffFilter/OnOffFilter.stories.tsx | 7 +- .../PlusButton/PlusButton.stories.tsx | 2 +- .../src/components/PlusButton/PlusButton.tsx | 4 + .../ResetButton/ResetButton.stories.tsx | 11 + .../SearchBar/SearchBar.stories.tsx | 7 +- .../SelectFilter/SelectFilter.stories.tsx | 11 +- .../components/SelectFilter/SelectFilter.tsx | 21 +- client/src/constants/errorMessage.ts | 2 + client/src/constants/hdongs.ts | 3518 +++++++++++++++++ client/src/constants/placeholder.ts | 2 + client/src/hooks/useAuth.ts | 25 + client/src/hooks/useFilter.ts | 15 +- client/src/hooks/useInput.ts | 44 +- client/src/hooks/useLocalAPI.ts | 10 +- client/src/hooks/useMaxPplInput.ts | 11 + client/src/hooks/usePlaceSearch.ts | 23 +- client/src/hooks/useShowMap.tsx | 24 +- client/src/hooks/useStartTimeInput.ts | 11 + client/src/hooks/useWriteMap.tsx | 7 +- client/src/pages/CourseDetail.tsx | 37 - .../{ => CourseDetail}/CourseDetail.styles.ts | 0 .../src/pages/CourseDetail/CourseDetail.tsx | 142 + client/src/pages/Courses.tsx | 107 - client/src/pages/Courses/Courses.tsx | 126 + client/src/pages/{ => Login}/Login.styles.ts | 0 client/src/pages/{ => Login}/Login.tsx | 7 +- .../pages/{MainPage.tsx => Menu/MenuPage.tsx} | 4 + client/src/pages/Mock.tsx | 159 + client/src/pages/NewCourse/NewCourse.tsx | 16 +- .../{ => RecruitDetail}/RecruitDetail.tsx | 46 +- client/src/pages/{ => Recruits}/Recruits.tsx | 73 +- client/src/pages/SignUp.tsx | 112 - .../src/pages/{ => SignUp}/SignUp.styles.ts | 7 +- client/src/pages/SignUp/SignUp.tsx | 71 + client/src/styles/flex.ts | 5 + client/src/types/{Local.ts => Address.ts} | 6 +- client/src/types/FilterOption.ts | 5 + client/src/types/LocalAPIType.ts | 5 + client/src/types/MapControlProps.ts | 4 +- client/src/types/maps/LatLngBounds.d.ts | 5 + client/src/utils/addressUtils.ts | 4 + client/src/utils/paceUtils.ts | 3 + client/src/utils/pathUtils.ts | 33 + ...{valitationUtils.ts => validationUtils.ts} | 6 +- dump.rdb | Bin 456 -> 0 bytes package-lock.json | 6 - server/.gitignore | 5 +- server/src/app.controller.ts | 17 - server/src/app.module.ts | 27 +- server/src/app.service.ts | 4 - server/src/auth/auth.controller.ts | 4 +- server/src/auth/auth.module.ts | 13 +- server/src/auth/auth.service.ts | 39 +- server/src/auth/dto/login-user.dto.ts | 2 +- .../{constant => constants}/error_message.ts | 0 .../date.validator.ts | 0 .../{decorator => decorators}/id.validator.ts | 0 .../common/{decorator => decorators}/index.ts | 0 .../path.validator.ts | 2 +- .../{decorator => decorators}/pw.validator.ts | 0 .../{ => common}/entities/course.entity.ts | 21 +- server/src/common/entities/h_dong.entity.ts | 14 + .../{ => common}/entities/recruit.entity.ts | 16 +- .../src/{ => common}/entities/user.entity.ts | 4 +- .../entities/user_recruit.entity.ts | 0 .../common/{guard => guards}/access.guard.ts | 6 +- .../common/{guard => guards}/refresh.guard.ts | 6 +- .../http-request-body.interceptor.ts | 22 + .../http-request/http-request-body.module.ts | 10 + .../modules/custom-jwt/custom-jwt.module.ts | 11 + .../modules/custom-jwt/custom-jwt.service.ts | 41 + .../repositories}/auth.repository.ts | 0 .../repositories}/course.repository.ts | 26 +- .../h_dong.repository.ts | 2 +- .../repositories}/recruit.repository.ts | 20 +- .../repositories}/user.repository.ts | 2 +- .../repositories}/user_recruit.repository.ts | 2 +- .../repository/user_recruit.repository.ts | 31 - .../course-data.ts} | 0 server/src/common/{type => types}/lat-lng.ts | 0 .../{type => types}/raw-recruit-data.ts | 2 +- .../src/common/utils/plainToGetRecruitDto.ts | 8 +- server/src/course/course.controller.ts | 15 +- server/src/course/course.module.ts | 4 +- server/src/course/course.service.ts | 29 +- server/src/course/dto/create-course.dto.ts | 11 +- server/src/entities/h_dong.entity.ts | 13 - server/src/main.ts | 6 +- .../create-recruit.request.ts} | 20 +- .../get-recruit.request.ts} | 0 .../join-recruit.request.ts} | 2 +- server/src/recruit/recruit.controller.ts | 39 +- server/src/recruit/recruit.module.ts | 21 +- server/src/recruit/recruit.service.ts | 64 +- server/src/user/dto/check-user.dto.ts | 2 +- server/src/user/dto/create-user.dto.ts | 4 +- server/src/user/user.module.ts | 5 +- server/src/user/user.service.ts | 6 +- 125 files changed, 4958 insertions(+), 770 deletions(-) create mode 100644 .gitignore create mode 100644 client/src/components/Input/AddressSearchInput/AddressSearchInput.tsx create mode 100644 client/src/components/Input/AddressSearchInput/Input.stories.tsx create mode 100644 client/src/components/Input/MaxPplInput/MaxPplInput.tsx create mode 100644 client/src/components/Input/StartTimeInput/StartTimeInput.stories.tsx create mode 100644 client/src/components/Input/StartTimeInput/StartTimeInput.tsx create mode 100644 client/src/components/MapControl/PlaceSearch/PlaceSearch.stories.tsx create mode 100644 client/src/components/ResetButton/ResetButton.stories.tsx create mode 100644 client/src/constants/hdongs.ts create mode 100644 client/src/hooks/useAuth.ts create mode 100644 client/src/hooks/useMaxPplInput.ts create mode 100644 client/src/hooks/useStartTimeInput.ts delete mode 100644 client/src/pages/CourseDetail.tsx rename client/src/pages/{ => CourseDetail}/CourseDetail.styles.ts (100%) create mode 100644 client/src/pages/CourseDetail/CourseDetail.tsx delete mode 100644 client/src/pages/Courses.tsx create mode 100644 client/src/pages/Courses/Courses.tsx rename client/src/pages/{ => Login}/Login.styles.ts (100%) rename client/src/pages/{ => Login}/Login.tsx (91%) rename client/src/pages/{MainPage.tsx => Menu/MenuPage.tsx} (89%) create mode 100644 client/src/pages/Mock.tsx rename client/src/pages/{ => RecruitDetail}/RecruitDetail.tsx (78%) rename client/src/pages/{ => Recruits}/Recruits.tsx (69%) delete mode 100644 client/src/pages/SignUp.tsx rename client/src/pages/{ => SignUp}/SignUp.styles.ts (87%) create mode 100644 client/src/pages/SignUp/SignUp.tsx rename client/src/types/{Local.ts => Address.ts} (82%) create mode 100644 client/src/types/FilterOption.ts create mode 100644 client/src/types/LocalAPIType.ts create mode 100644 client/src/types/maps/LatLngBounds.d.ts create mode 100644 client/src/utils/addressUtils.ts create mode 100644 client/src/utils/paceUtils.ts create mode 100644 client/src/utils/pathUtils.ts rename client/src/utils/{valitationUtils.ts => validationUtils.ts} (83%) delete mode 100644 dump.rdb delete mode 100644 package-lock.json delete mode 100644 server/src/app.controller.ts delete mode 100644 server/src/app.service.ts rename server/src/common/{constant => constants}/error_message.ts (100%) rename server/src/common/{decorator => decorators}/date.validator.ts (100%) rename server/src/common/{decorator => decorators}/id.validator.ts (100%) rename server/src/common/{decorator => decorators}/index.ts (100%) rename server/src/common/{decorator => decorators}/path.validator.ts (95%) rename server/src/common/{decorator => decorators}/pw.validator.ts (100%) rename server/src/{ => common}/entities/course.entity.ts (73%) create mode 100644 server/src/common/entities/h_dong.entity.ts rename server/src/{ => common}/entities/recruit.entity.ts (80%) rename server/src/{ => common}/entities/user.entity.ts (88%) rename server/src/{ => common}/entities/user_recruit.entity.ts (100%) rename server/src/common/{guard => guards}/access.guard.ts (76%) rename server/src/common/{guard => guards}/refresh.guard.ts (75%) create mode 100644 server/src/common/interceptors/http-request/http-request-body.interceptor.ts create mode 100644 server/src/common/interceptors/http-request/http-request-body.module.ts create mode 100644 server/src/common/modules/custom-jwt/custom-jwt.module.ts create mode 100644 server/src/common/modules/custom-jwt/custom-jwt.service.ts rename server/src/{auth => common/repositories}/auth.repository.ts (100%) rename server/src/{course => common/repositories}/course.repository.ts (67%) rename server/src/common/{repository => repositories}/h_dong.repository.ts (76%) rename server/src/{recruit => common/repositories}/recruit.repository.ts (88%) rename server/src/{user => common/repositories}/user.repository.ts (89%) rename server/src/{ => common/repositories}/user_recruit.repository.ts (93%) delete mode 100644 server/src/common/repository/user_recruit.repository.ts rename server/src/common/{type/raw-course-data.ts => types/course-data.ts} (100%) rename server/src/common/{type => types}/lat-lng.ts (100%) rename server/src/common/{type => types}/raw-recruit-data.ts (93%) delete mode 100644 server/src/entities/h_dong.entity.ts rename server/src/recruit/dto/{create-recruit.dto.ts => request/create-recruit.request.ts} (54%) rename server/src/recruit/dto/{get-recruit.dto.ts => request/get-recruit.request.ts} (100%) rename server/src/recruit/dto/{join-recruit.dto.ts => request/join-recruit.request.ts} (86%) diff --git a/.github/workflows/develop-check.yml b/.github/workflows/develop-check.yml index 901e5f3..caa7624 100644 --- a/.github/workflows/develop-check.yml +++ b/.github/workflows/develop-check.yml @@ -1,33 +1,79 @@ -name : build + eslint check +name: build + eslint check on: - pull_request: - branches: - - develop + pull_request: + branches: + - develop jobs: - check: - name: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install client dependencies - run : | - cd client - npm install - - name: Install server dependencies - run: | - cd server - npm install - - name: Check eslint - run: | - ./client/node_modules/.bin/eslint client/src --ext .ts,.tsx - ./server/node_modules/.bin/eslint server/src --ext .ts,.tsx - - name: Run client build check - run: | - cd client - npm run build - - name: Run server build check - run: | - cd server - npm run build + check: + name: check + runs-on: ubuntu-latest + outputs: + client: ${{steps.filter.outputs.client}} + server: ${{steps.filter.outputs.server}} + steps: + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + client: + - 'client/**' + server: + - 'server/**' + + client: + needs: check + if: ${{needs.check.outputs.client=='true'}} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Nodejs Setup + uses: actions/setup-node@v2 + + - name: Cache client node modules + id: cache-client + uses: actions/cache@v3 + with: + path: client/node_modules + key: npm-packages-client-${{hashFiles('**/package-lock.json')}} + + - name: Install client dependencies + if: ${{steps.cache-client.outputs.cache-hit != 'true'}} + run: cd client && npm install + + - name: Client eslint check + run: ./client/node_modules/.bin/eslint client/src --ext .ts,.tsx + + - name: Run client build check + run: cd client && npm run build + + server: + needs: check + if: ${{needs.check.outputs.server=='true'}} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Nodejs Setup + uses: actions/setup-node@v2 + + - name: Cache server node modules + id: cache-server + uses: actions/cache@v3 + with: + path: server/node_modules + key: npm-packages-server-${{hashFiles('**/package-lock.json')}} + + - name: Install server dependencies + if: ${{steps.cache-server.outputs.cache-hit != 'true'}} + run: cd server && npm install + + - name: server eslint check + run: ./server/node_modules/.bin/eslint server/src --ext .ts,.tsx + + - name: Run server build check + run: cd server && npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c98bda --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# dump +dump.* \ No newline at end of file diff --git a/client/.storybook/main.js b/client/.storybook/main.js index af63599..13f2dda 100644 --- a/client/.storybook/main.js +++ b/client/.storybook/main.js @@ -22,6 +22,7 @@ module.exports = { "#assets": path.resolve(__dirname, "../src/assets"), "#constants": path.resolve(__dirname, "../src/constants"), "#types": path.resolve(__dirname, "../src/types"), + "#atoms": path.resolve(__dirname, "../src/atoms"), "styles/*": path.resolve(__dirname, "../src/styles"), }; diff --git a/client/package-lock.json b/client/package-lock.json index e452a2d..b3eca17 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@faker-js/faker": "^7.6.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -2638,6 +2639,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -33928,6 +33938,11 @@ } } }, + "@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==" + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", diff --git a/client/package.json b/client/package.json index efc5999..3251541 100644 --- a/client/package.json +++ b/client/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@faker-js/faker": "^7.6.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/client/src/App.tsx b/client/src/App.tsx index e712f32..eb1a382 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,18 +1,19 @@ import React, { useCallback, useEffect, useState } from "react"; -import SignUp from "#pages/SignUp"; -import Login from "#pages/Login"; -import MainPage from "#pages/MainPage"; -import MainPage2 from "#pages/MainPage/MainPage"; -import Courses from "#pages/Courses"; +import SignUp from "#pages/SignUp/SignUp"; +import Login from "#pages/Login/Login"; +import MenuPage from "#pages/Menu/MenuPage"; +import MainPage from "#pages/Main2/MainPage"; +import Courses from "#pages/Courses/Courses"; import { useRecoilState } from "recoil"; import { userState } from "#atoms/userState"; import { Route, Routes } from "react-router-dom"; import NewCourse from "#pages/NewCourse/NewCourse"; -import RecruitDetail from "#pages/RecruitDetail"; -import CourseDetail from "#pages/CourseDetail"; +import RecruitDetail from "#pages/RecruitDetail/RecruitDetail"; +import CourseDetail from "#pages/CourseDetail/CourseDetail"; import useGet from "#hooks/http/useHttpGet"; import { TIME } from "#constants/time"; -import Recruits from "#pages/Recruits"; +import Mock from "#pages/Mock"; +import Recruits from "#pages/Recruits/Recruits"; function App() { const [userInfo, setUserInfo] = useRecoilState(userState); @@ -47,7 +48,7 @@ function App() { return ( } /> - } /> + } /> } /> } /> } /> @@ -59,6 +60,9 @@ function App() { } /> + + } /> + ); } diff --git a/client/src/components/AddressList/AddressList.tsx b/client/src/components/AddressList/AddressList.tsx index f207941..729ea9c 100644 --- a/client/src/components/AddressList/AddressList.tsx +++ b/client/src/components/AddressList/AddressList.tsx @@ -1,17 +1,17 @@ -import { LocalData } from "#types/Local"; +import { Address } from "#types/Address"; import { MouseEventHandler } from "react"; import { PlaceList } from "./AddressList.styles"; interface AddressListProps { - data: LocalData[]; - onClickLocal: (local: LocalData) => MouseEventHandler; + data: Address[]; + onClickAddress: (address: Address) => MouseEventHandler; } -const AddressList = ({ data, onClickLocal }: AddressListProps) => { +const AddressList = ({ data, onClickAddress }: AddressListProps) => { return ( - {data.map((local) => ( -
  • - {local.address_name} + {data.map((address) => ( +
  • + {address.address_name}
  • ))}
    diff --git a/client/src/components/Button/Button.stories.tsx b/client/src/components/Button/Button.stories.tsx index 97ba146..b67bfb2 100644 --- a/client/src/components/Button/Button.stories.tsx +++ b/client/src/components/Button/Button.stories.tsx @@ -4,10 +4,9 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import Button from "./Button"; export default { - title: "Example/Button", + title: "Button/Button", component: Button, } as ComponentMeta; - const Template: ComponentStory = (args) => diff --git a/client/src/components/Modal/Modal.styles.ts b/client/src/components/Modal/Modal.styles.ts index 769a252..4141bf9 100644 --- a/client/src/components/Modal/Modal.styles.ts +++ b/client/src/components/Modal/Modal.styles.ts @@ -4,9 +4,10 @@ import { flexRowCenter } from "styles/flex"; export const Dimmed = styled.div` ${flexRowCenter} + z-index: 1; width: 100vw; height: 100vh; - position: absolute; + position: fixed; left: 0; top: 0; background-color: rgba(0, 0, 0, 0.3); diff --git a/client/src/components/MoreButton/MoreButton.stories.tsx b/client/src/components/MoreButton/MoreButton.stories.tsx index fb17fce..55192ee 100644 --- a/client/src/components/MoreButton/MoreButton.stories.tsx +++ b/client/src/components/MoreButton/MoreButton.stories.tsx @@ -5,7 +5,7 @@ import MoreButton from "./MoreButton"; import { MemoryRouter } from "react-router-dom"; export default { - title: "Example/MoreButton", + title: "Button/MoreButton", component: MoreButton, } as ComponentMeta; diff --git a/client/src/components/OnOffFilter/OnOffFilter.stories.tsx b/client/src/components/OnOffFilter/OnOffFilter.stories.tsx index 3f9991f..0640846 100644 --- a/client/src/components/OnOffFilter/OnOffFilter.stories.tsx +++ b/client/src/components/OnOffFilter/OnOffFilter.stories.tsx @@ -4,14 +4,13 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import OnOffFilter from "./OnOffFilter"; export default { - title: "Example/OnOffFilter", + title: "Filter/OnOffFilter", component: OnOffFilter, } as ComponentMeta; -const Template: ComponentStory = (args) => ; +export const _OnOffFilter: ComponentStory = (args) => ; -export const Text = Template.bind({}); -Text.args = { +_OnOffFilter.args = { filterState: true, filterName: "제목", toggleFilterState: console.log, diff --git a/client/src/components/PlusButton/PlusButton.stories.tsx b/client/src/components/PlusButton/PlusButton.stories.tsx index 1b748dd..04568d2 100644 --- a/client/src/components/PlusButton/PlusButton.stories.tsx +++ b/client/src/components/PlusButton/PlusButton.stories.tsx @@ -4,7 +4,7 @@ import { MemoryRouter } from "react-router-dom"; import PlusButton from "./PlusButton"; export default { - title: "Example/PlusButton", + title: "Button/PlusButton", component: PlusButton, } as ComponentMeta; diff --git a/client/src/components/PlusButton/PlusButton.tsx b/client/src/components/PlusButton/PlusButton.tsx index aa59ecc..823db15 100644 --- a/client/src/components/PlusButton/PlusButton.tsx +++ b/client/src/components/PlusButton/PlusButton.tsx @@ -1,5 +1,7 @@ import { PLUS_BUTTON_ICON } from "#assets/icons"; +import { userState } from "#atoms/userState"; import { useNavigate } from "react-router-dom"; +import { useRecoilValue } from "recoil"; import styled from "styled-components"; const Button = styled.img` @@ -15,6 +17,8 @@ interface PlusButtonProps { } const PlusButton = ({ to }: PlusButtonProps) => { const navigate = useNavigate(); + const user = useRecoilValue(userState); + if (!user.accessToken) return <>; return - - - ); -}; - -export default CourseDetail; diff --git a/client/src/pages/CourseDetail.styles.ts b/client/src/pages/CourseDetail/CourseDetail.styles.ts similarity index 100% rename from client/src/pages/CourseDetail.styles.ts rename to client/src/pages/CourseDetail/CourseDetail.styles.ts diff --git a/client/src/pages/CourseDetail/CourseDetail.tsx b/client/src/pages/CourseDetail/CourseDetail.tsx new file mode 100644 index 0000000..e47a14e --- /dev/null +++ b/client/src/pages/CourseDetail/CourseDetail.tsx @@ -0,0 +1,142 @@ +import Header from "#components/Header/Header"; +import Button from "#components/Button/Button"; +import { Content, Title } from "./CourseDetail.styles"; +import Modal from "#components/Modal/Modal"; +import { useCallback, useEffect, useState } from "react"; +import Input from "#components/Input/Input"; +import { COLOR } from "styles/color"; +import styled from "styled-components"; +import { flexRowSpaceAround } from "styles/flex"; +import { PLACEHOLDER } from "#constants/placeholder"; +import PaceInput from "#components/Input/PaceInput/PaceInput"; +import usePaceInput from "#hooks/usePaceInput"; +import useInput from "#hooks/useInput"; +import { recruitTitleValidator } from "#utils/validationUtils"; +import useHttpPost from "#hooks/http/useHttpPost"; +import { useNavigate, useParams } from "react-router-dom"; +import StartTimeInput from "#components/Input/StartTimeInput/StartTimeInput"; +import MaxPplInput from "#components/Input/MaxPplInput/MaxPplInput"; +import useStartTimeInput from "#hooks/useStartTimeInput"; +import useMaxPplInput from "#hooks/useMaxPplInput"; +import useHttpGet from "#hooks/http/useHttpGet"; +import { InputWrapper } from "#pages/SignUp/SignUp.styles"; +import { useRecoilValue } from "recoil"; +import { userState } from "#atoms/userState"; +import useShowMap from "#hooks/useShowMap"; +import { getMiddlePoint } from "#utils/pathUtils"; + +const Buttons = styled.div` + ${flexRowSpaceAround} + padding-top: 16px; +`; + +const CourseDetail = () => { + const userInfo = useRecoilValue(userState); + const [courseTitle, setCourseTitle] = useState("제목"); + const [startPoint, setStartPoint] = useState("출발점"); + const [totalLength, setTotalLength] = useState(0); + const [author, setAuthor] = useState("게시자"); + + const [path, setPath] = useState([]); + + const [title, onChangeTitle, titleError] = useInput(recruitTitleValidator); + const { pace, onChangeMinute, onChangeSecond } = usePaceInput(); + const { startTime, onChangeStartTime } = useStartTimeInput(); + const { maxPpl, onChangeMaxPpl } = useMaxPplInput(); + + const renderMap = useCallback( + useShowMap({ + height: `70vh`, + center: getMiddlePoint(path), + runningPath: path, + }).renderMap, + [path], + ); + + const checkFormValidation = () => title && maxPpl && startTime && pace; + + const [showModal, setShowModal] = useState(false); + const { post } = useHttpPost(); + const { get } = useHttpGet(); + const { id } = useParams(); + const navigate = useNavigate(); + + const handleToggleModal = () => { + setShowModal(!showModal); + }; + + const onSubmitRecruit = async () => { + if (!checkFormValidation()) return; + try { + const { data } = await post("/recruit", { + title, + courseId: id, + startTime, + maxPpl, + pace: pace.minute * 60 + pace.second, + userId: userInfo.userIdx, + }); + + navigate(`/recruit/${data.recruitId}`); + } catch (error: any) { + alert(error.message); + } + }; + + const getCourseDetail = useCallback(async () => { + try { + const response = await get(`/course/${id}`); + setCourseTitle(response.title); + setTotalLength(response.pathLength / 1000); + setStartPoint(response.hDong.name); + setAuthor(response.userId); + setPath(JSON.parse(response.path)); + } catch {} + }, []); + + useEffect(() => { + getCourseDetail(); + }, []); + + return ( + <> +
    + {renderMap()} + {courseTitle} + +
    + 출발점 +

    {startPoint}

    +
    +
    + 총 거리 +

    {totalLength}km

    +
    +
    + 게시자 +

    {author}

    +
    + +
    + + + + {titleError} + + + + + + + + + + + ); +}; + +export default CourseDetail; diff --git a/client/src/pages/Courses.tsx b/client/src/pages/Courses.tsx deleted file mode 100644 index baeb527..0000000 --- a/client/src/pages/Courses.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState, useEffect } from "react"; -import Header from "#components/Header/Header"; -import SearchBar from "#components/SearchBar/SearchBar"; -import FilterBar from "#components/FilterBar/FilterBar"; -import SelectFilter from "#components/SelectFilter/SelectFilter"; -import useFilter from "#hooks/useFilter"; -import OnOffFilter from "#components/OnOffFilter/OnOffFilter"; -import useOnOffFilter from "#hooks/useOnOffFilter"; -import { PLACEHOLDER } from "#constants/placeholder"; -import { CLOCK_ICON, LOCATION_ICON } from "#assets/icons"; - -const DummyCardData = { - title: "황새울공원 한 바퀴 도는 코스입니다.", - courseId: 5, - path: [ - { lat: 126.57091836134346, lng: 33.45090000378721 }, - { lat: 126.57004847387998, lng: 33.450635526049844 }, - { lat: 126.56931524544794, lng: 33.45101165404891 }, - { lat: 126.56932224193068, lng: 33.44959616387136 }, - { lat: 126.5700747443057, lng: 33.449670903389 }, - { lat: 126.570502727405, lng: 33.450123187413496 }, - ], - pathLength: 3355, - userId: "gchoi96", - img: "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png", - hCode: "신림동", -}; - -const Courses = () => { - const [currentDistanceFilter, setCurrentDistanceFilter] = useFilter("5km 이내"); - const [currentTimeFilter, setCurrentTimeFilter] = useFilter("5시간 이내"); - - const [titleFilter, toggleTitleFilter] = useOnOffFilter(true); - const [authorFilter, toggleAuthorFilter] = useOnOffFilter(true); - const [contentFilter, toggleContentFilter] = useOnOffFilter(true); - const [availFilter, toggleAvailFilter] = useOnOffFilter(true); - - const [cardList, setCardList] = useState([]); - - const sendRequest = () => { - const params = new URLSearchParams({ - dist: "5", - maxLen: "3000", - minLen: "1000", - page: "1", - title: "true", - author: "true", - }); - console.log(params.toString()); - }; - - //fake API for infinite scroll - const fetchNextData = () => { - setTimeout(() => { - setCardList(cardList.concat(Array(5).fill(DummyCardData))); - }, 2000); - }; - - useEffect(() => { - fetchNextData(); - sendRequest(); - }, []); - - return ( - <> -
    - - - - - - - - - - - ); -}; -export default Courses; diff --git a/client/src/pages/Courses/Courses.tsx b/client/src/pages/Courses/Courses.tsx new file mode 100644 index 0000000..c51e575 --- /dev/null +++ b/client/src/pages/Courses/Courses.tsx @@ -0,0 +1,126 @@ +import React, { useState, useRef, useEffect } from "react"; +import Header from "#components/Header/Header"; +import SearchBar from "#components/SearchBar/SearchBar"; +import FilterBar from "#components/FilterBar/FilterBar"; +import SelectFilter from "#components/SelectFilter/SelectFilter"; +import useFilter from "#hooks/useFilter"; +import OnOffFilter from "#components/OnOffFilter/OnOffFilter"; +import useOnOffFilter from "#hooks/useOnOffFilter"; +import { PLACEHOLDER } from "#constants/placeholder"; +import styled from "styled-components"; +import InfiniteScroll from "react-infinite-scroll-component"; +import useGet from "#hooks/http/useHttpGet"; +import { LOCATION_ICON } from "#assets/icons"; +import CourseCard from "#components/Card/CourseCard/CourseCard"; +import { Course } from "#types/Course"; + +const CourseList = styled.div` + padding: 2rem; + display: flex; + flex-direction: column; + gap: 2rem; +`; + +const Courses = () => { + const { get } = useGet(); + const [currentDistanceFilter, setCurrentDistanceFilter] = useFilter({ text: "3-5KM", min: 3, max: 5 }); + + const [titleFilter, toggleTitleFilter] = useOnOffFilter(false); + const [authorFilter, toggleAuthorFilter] = useOnOffFilter(false); + + const [cardList, setCardList] = useState([]); + const [searchContent, setSearchContent] = useState(""); + const handleSearchContentChange = (e: React.FormEvent) => { + setSearchContent(e.currentTarget.value); + }; + const page = useRef(1); + const incrementPage = () => { + page.current++; + }; + const [hasMore, setHasMore] = useState(true); + + const courseQueryParams = () => { + const param: any = {}; + if (titleFilter) param.title = "true"; + if (authorFilter) param.author = "true"; + if (searchContent !== "") param.query = searchContent; + param.maxLen = (currentDistanceFilter.max * 1000).toString(); + param.minLen = (currentDistanceFilter.min * 1000).toString(); + param.page = page.current.toString(); + return param; + }; + + const sendCourseFetchRequest = async () => { + const response = await get("/course", courseQueryParams()); + if (response.data.length == 0) setHasMore(false); + + setCardList((prev) => [...prev, ...response.data]); + incrementPage(); + }; + + const resetSearchResultCards = () => { + page.current = 1; + setCardList([]); + }; + + useEffect(() => { + sendCourseFetchRequest(); + }, []); + + useEffect(() => { + console.log(cardList.length); + }, [cardList]); + return ( + <> +
    + { + resetSearchResultCards(); + sendCourseFetchRequest(); + }} + content={searchContent} + onChange={handleSearchContentChange} + > + + + + + + { + if (!hasMore) return; + sendCourseFetchRequest(); + }} + hasMore={hasMore} + loader={

    Loading...

    } + > + + {cardList.map((card, i) => ( + + ))} + +
    + + ); +}; +export default Courses; diff --git a/client/src/pages/Login.styles.ts b/client/src/pages/Login/Login.styles.ts similarity index 100% rename from client/src/pages/Login.styles.ts rename to client/src/pages/Login/Login.styles.ts diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login/Login.tsx similarity index 91% rename from client/src/pages/Login.tsx rename to client/src/pages/Login/Login.tsx index 3e31236..8fa381f 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login/Login.tsx @@ -5,16 +5,17 @@ import Input from "#components/Input/Input"; import Button from "#components/Button/Button"; import useInput from "#hooks/useInput"; import { PLACEHOLDER } from "#constants/placeholder"; -import { idValidator, passwordValidator } from "#utils/valitationUtils"; +import { idValidator, passwordValidator } from "#utils/validationUtils"; import { useSetRecoilState } from "recoil"; -import { InputWrapper, OptionsWrapper } from "./SignUp.styles"; import { userState } from "#atoms/userState"; - import { LogoWrapper } from "./Login.styles"; import useHttpPost from "#hooks/http/useHttpPost"; +import useAuth from "#hooks/useAuth"; +import { InputWrapper, OptionsWrapper } from "#pages/SignUp/SignUp.styles"; const Login = () => { + useAuth(false); const [userId, onChangeUserId, userIdError] = useInput(idValidator); const [password, onChangePassword, passwordError] = useInput(passwordValidator); const { post } = useHttpPost(); diff --git a/client/src/pages/MainPage.tsx b/client/src/pages/Menu/MenuPage.tsx similarity index 89% rename from client/src/pages/MainPage.tsx rename to client/src/pages/Menu/MenuPage.tsx index d3874d7..6e189b2 100644 --- a/client/src/pages/MainPage.tsx +++ b/client/src/pages/Menu/MenuPage.tsx @@ -30,6 +30,9 @@ const MainPage = () => { const handleMainPage = () => { navigate("/main"); }; + const handleCourseMockClick = () => { + navigate("/mock/course"); + }; return ( <> @@ -41,6 +44,7 @@ const MainPage = () => { + ); }; diff --git a/client/src/pages/Mock.tsx b/client/src/pages/Mock.tsx new file mode 100644 index 0000000..da6e7f3 --- /dev/null +++ b/client/src/pages/Mock.tsx @@ -0,0 +1,159 @@ +import { useState } from "react"; +import styled from "styled-components"; +import { flexRowCenter } from "styles/flex"; +import { faker } from "@faker-js/faker"; +import usePost from "#hooks/http/useHttpPost"; +import { hdongs } from "#constants/hdongs"; +export const CenterWrapper = styled.div` + ${flexRowCenter} + flex-direction: column; + gap: 10px; + input { + margin-left: 10px; + } + label { + display: flex; + flex-direction: column; + gap: 10px; + } +`; + +export const InputWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + input { + width: 200px; + } +`; + +export const Input = styled.div` + display: flex; +`; +const jejuExURL = + "https://user-images.githubusercontent.com/53655119/204238717-6b1ff83d-6d85-4ab8-9b4f-056947147706.png"; +const Mock = () => { + const [reps, setReps] = useState(0); + const [maxLat, setMaxLat] = useState(33.462789834085406); + const [maxLng, setMaxLng] = useState(126.58443060603571); + const [minLat, setMinLat] = useState(33.44850043013376); + const [minLng, setMinLng] = useState(126.56573142547181); + const [minLen, setMinLen] = useState(0); + const [maxLen, setMaxLen] = useState(5000); + const [pathPoints, setPathPoints] = useState(5); + const { post } = usePost(); + + const handleMinLenChange = (e: any) => { + setMinLen(e.target.value); + }; + + const handleMaxLenChange = (e: any) => { + setMaxLen(e.target.value); + }; + + const handleRepsChange = (e: any) => { + setReps(e.target.value); + }; + const handleMaxLatChange = (e: any) => { + setMaxLat(e.target.value); + }; + const handleMinLatChange = (e: any) => { + setMinLat(e.target.value); + }; + const handleMaxLngChange = (e: any) => { + setMaxLng(e.target.value); + }; + const handleMinLngChange = (e: any) => { + setMinLng(e.target.value); + }; + const handlePathPointsChange = (e: any) => { + setPathPoints(e.target.value); + }; + + const generateRandomCourse = () => { + const randomTitle = faker.lorem.sentences(faker.datatype.number({ max: 3, min: 1 })); + + const randomPath = []; + + for (let i = 0; i < pathPoints; i++) { + const randQa = faker.datatype.float({ + min: minLng, + max: maxLng, + precision: 0.00000000000001, + }); + + const randMa = faker.datatype.float({ + min: minLat, + max: maxLat, + precision: 0.00000000000001, + }); + randomPath.push({ lat: randMa, lng: randQa }); + } + const randomImg = faker.image.cats(680, 480, true); + const maxHdong = hdongs.length; + const randomHcodeIdx = faker.datatype.number({ max: maxHdong - 1, min: 0 }); + const randomCourse = { + title: randomTitle, + img: randomImg, + path: randomPath, + userId: 1, + pathLength: faker.datatype.number({ min: minLen, max: maxLen }), + hCode: hdongs[randomHcodeIdx][0], + }; + return randomCourse; + }; + + const sendAxiosRequest = async (course: any) => { + await post("/course", course); + }; + + const handleSubmit = async () => { + for (let i = 0; i < reps; i++) { + sendAxiosRequest(generateRandomCourse()); + } + }; + + return ( + <> + +
    코스데이터 생성기
    +
    + 반복횟수 + +
    + + + 경도(longitude) + + ~ + + + + 위도(latitude)    + + ~ + + +
    + + 총 길이 + + ~ + + +
    + + 경로의 점 수 + +
    + +
    제주도 예시
    +
    (lng) min: 33.44850043013376, max: 33.462789834085406
    +
    (lat)min: 126.56573142547181, max: 126.58443060603571
    + +
    + + ); +}; + +export default Mock; diff --git a/client/src/pages/NewCourse/NewCourse.tsx b/client/src/pages/NewCourse/NewCourse.tsx index 52a8ab1..5f31ad5 100644 --- a/client/src/pages/NewCourse/NewCourse.tsx +++ b/client/src/pages/NewCourse/NewCourse.tsx @@ -1,4 +1,3 @@ -//#region import import Button from "#components/Button/Button"; import Header from "#components/Header/Header"; import Input from "#components/Input/Input"; @@ -9,19 +8,20 @@ import useLocalAPI from "#hooks/useLocalAPI"; import getLatLngByXY from "#utils/mapUtils"; import { PLACEHOLDER } from "#constants/placeholder"; import { useCallback } from "react"; -import { useRecoilValue } from "recoil"; -import { userState } from "#atoms/userState"; import { useNavigate } from "react-router-dom"; -import { courseTitleValidator } from "#utils/valitationUtils"; +import { courseTitleValidator } from "#utils/validationUtils"; import { RegionResponse } from "#types/Region"; import { CourseForm } from "./NewCourse.styles"; +import useAuth from "#hooks/useAuth"; + +import { LOCAL_API_PATH } from "#types/LocalAPIType"; //#endregion const img = "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png"; const NewCourse = () => { - const { userIdx: userId } = useRecoilValue(userState); + const { userIdx: userId } = useAuth(); const [title, onChangeTitle] = useInput(courseTitleValidator); - const query = useLocalAPI("/geo/coord2regioncode.json"); + const query = useLocalAPI(LOCAL_API_PATH.REGION_CODE); const { post } = useHttpPost(); const navigate = useNavigate(); @@ -46,7 +46,7 @@ const NewCourse = () => { hCode, name, }); - navigate(`/course/${response.courseId}`); + navigate(`/course/${response.data.courseId}`); } catch (error: any) { alert(error.message); } @@ -54,7 +54,7 @@ const NewCourse = () => { return (
    -
    +
    {renderMap()}
    diff --git a/client/src/pages/RecruitDetail.tsx b/client/src/pages/RecruitDetail/RecruitDetail.tsx similarity index 78% rename from client/src/pages/RecruitDetail.tsx rename to client/src/pages/RecruitDetail/RecruitDetail.tsx index da5ad1c..cffbbf2 100644 --- a/client/src/pages/RecruitDetail.tsx +++ b/client/src/pages/RecruitDetail/RecruitDetail.tsx @@ -1,6 +1,6 @@ import Header from "#components/Header/Header"; import Button from "#components/Button/Button"; -import { Content, Title } from "./RecruitDetail.styles"; +import { Content, Title } from "../RecruitDetail.styles"; import { useParams } from "react-router-dom"; import { userState } from "#atoms/userState"; import { useRecoilValue } from "recoil"; @@ -8,6 +8,8 @@ import useHttpPost from "#hooks/http/useHttpPost"; import useHttpGet from "#hooks/http/useHttpGet"; import { useEffect, useState, useCallback } from "react"; import useShowMap from "#hooks/useShowMap"; +import { getPaceFormat } from "#utils/paceUtils"; +import { getMiddlePoint } from "#utils/pathUtils"; const RecruitDetail = () => { const { id } = useParams(); @@ -16,7 +18,8 @@ const RecruitDetail = () => { const { get } = useHttpGet(); const [title, setTitle] = useState("제목"); const [startPoint, setStartPoint] = useState("출발점"); - const [pace, setPace] = useState("페이스ㄴ"); + const [pathLength, setPathLength] = useState(0); + const [pace, setPace] = useState("페이스"); const [startTime, setStartTime] = useState("집합 일시"); const [author, setAuthor] = useState("게시자"); const [maxPpl, setMaxPpl] = useState("최대 인원"); @@ -24,10 +27,6 @@ const RecruitDetail = () => { const [path, setPath] = useState([]); const [middlePoint, setMiddlePoint] = useState({ lat: 0, lng: 0 }); - const getPaceFormat = (sec: number): string => { - return `${parseInt(String(sec / 60))}'${sec % 60}"`; - }; - const getTimeFormat = (timeZone: string): string => { const date = timeZone.split("T")[0].split("-"); const time = timeZone.split("T")[1].split(":"); @@ -35,6 +34,7 @@ const RecruitDetail = () => { time[1] }분`; }; + const onSubmitJoin = async () => { try { await post("/recruit/join", { @@ -46,6 +46,7 @@ const RecruitDetail = () => { alert(error.message); } }; + const renderMap = useCallback( useShowMap({ height: `${window.innerHeight - 307}px`, @@ -53,34 +54,15 @@ const RecruitDetail = () => { runningPath: path, level: 5, }).renderMap, - [path, middlePoint], + [path], ); - const getMiddlePoint = (path: { lat: number; lng: number }[]) => { - let minLat = 90; - let maxLat = -90; - let minLng = 180; - let maxLng = -180; - for (const point of path) { - if (minLat > point.lat) { - minLat = point.lat; - } - if (maxLat < point.lat) { - maxLat = point.lat; - } - if (minLng > point.lng) { - minLng = point.lng; - } - if (maxLng < point.lng) { - maxLng = point.lng; - } - } - return { lat: (minLat + maxLat) / 2, lng: (minLng + maxLng) / 2 }; - }; + const getRecruitDetail = useCallback(async () => { try { const response = await get(`/recruit/${id}`); setTitle(response.title); - setStartPoint(response.name); + setPathLength(response.pathLength / 1000); + setStartPoint(response.hDong.name); setPace(getPaceFormat(response.pace)); setStartTime(getTimeFormat(response.startTime)); setAuthor(response.userId); @@ -99,7 +81,7 @@ const RecruitDetail = () => { }, [userInfo]); return ( <> -
    +
    {renderMap()} {title} @@ -107,6 +89,10 @@ const RecruitDetail = () => { 출발점

    {startPoint}

    +
    + 총거리 +

    {pathLength}km

    +
    페이스

    {pace}/km

    diff --git a/client/src/pages/Recruits.tsx b/client/src/pages/Recruits/Recruits.tsx similarity index 69% rename from client/src/pages/Recruits.tsx rename to client/src/pages/Recruits/Recruits.tsx index abb13d5..56ab31f 100644 --- a/client/src/pages/Recruits.tsx +++ b/client/src/pages/Recruits/Recruits.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useRef, useEffect } from "react"; import Header from "#components/Header/Header"; import SearchBar from "#components/SearchBar/SearchBar"; import FilterBar from "#components/FilterBar/FilterBar"; @@ -7,12 +7,12 @@ import useFilter from "#hooks/useFilter"; import OnOffFilter from "#components/OnOffFilter/OnOffFilter"; import useOnOffFilter from "#hooks/useOnOffFilter"; import { PLACEHOLDER } from "#constants/placeholder"; -import { hasNumber } from "#utils/stringtils"; import styled from "styled-components"; import InfiniteScroll from "react-infinite-scroll-component"; import useGet from "#hooks/http/useHttpGet"; import { LOCATION_ICON, CLOCK_ICON } from "#assets/icons"; import RecruitCard from "#components/Card/RecruitCard/RecruitCard"; +import { Recruit } from "#types/Recruit"; const RecruitList = styled.div` padding: 2rem; @@ -22,50 +22,56 @@ const RecruitList = styled.div` `; const Recruits = () => { - const [currentDistanceFilter, setCurrentDistanceFilter] = useFilter("선택 없음"); - const [currentTimeFilter, setCurrentTimeFilter] = useFilter("선택 없음"); + const { get } = useGet(); + const [currentDistanceFilter, setCurrentDistanceFilter] = useFilter({ text: "3-5KM", min: 3, max: 5 }); + const [currentTimeFilter, setCurrentTimeFilter] = useFilter({ text: "3시간 이내", min: 0, max: 3 }); const [titleFilter, toggleTitleFilter] = useOnOffFilter(false); const [authorFilter, toggleAuthorFilter] = useOnOffFilter(false); const [availFilter, toggleAvailFilter] = useOnOffFilter(false); - const [cardList, setCardList] = useState([]); + const [cardList, setCardList] = useState([]); const [searchContent, setSearchContent] = useState(""); const handleSearchContentChange = (e: React.FormEvent) => { setSearchContent(e.currentTarget.value); }; - const [page, setPage] = useState(1); + const page = useRef(1); const incrementPage = () => { - setPage(page + 1); + page.current++; }; - const { get } = useGet(); + const [hasMore, setHasMore] = useState(true); - const sendRequest = async () => { - const maxLen = Number(currentDistanceFilter[0]); - const time = Number(currentTimeFilter[0]); - let minLen = maxLen - 2; - if (maxLen === 1) minLen = 0; + const recruitQueryParams = () => { const param: any = {}; if (titleFilter) param.title = "true"; if (authorFilter) param.author = "true"; if (availFilter) param.avail = "true"; - if (searchContent !== "") param.query = searchContent; - if (hasNumber(currentDistanceFilter) || hasNumber(currentTimeFilter)) { - param.maxLen = (maxLen * 1000).toString(); - param.minLen = (minLen * 1000).toString(); - time: time.toString(); - } - const response = await get("/recruit", param); + param.maxLen = (currentDistanceFilter.max * 1000).toString(); + param.minLen = (currentDistanceFilter.min * 1000).toString(); + param.time = currentTimeFilter.max.toString(); + param.page = page.current.toString(); + return param; + }; + + const sendRecruitFetchRequest = async () => { + const response = await get("/recruit", recruitQueryParams()); + if (response.data.length == 0) setHasMore(false); setCardList((prev) => [...prev, ...response.data]); + incrementPage(); + }; + + const resetSearchResultCards = () => { + page.current = 1; + setCardList([]); }; useEffect(() => { - sendRequest(); + sendRecruitFetchRequest(); }, []); return ( @@ -74,11 +80,8 @@ const Recruits = () => { { - console.log("///"); - setPage(1); - setCardList([]); - console.log(cardList); - sendRequest(); + resetSearchResultCards(); + sendRecruitFetchRequest(); }} content={searchContent} onChange={handleSearchContentChange} @@ -102,14 +105,22 @@ const Recruits = () => { @@ -117,10 +128,10 @@ const Recruits = () => { { - incrementPage(); - sendRequest(); + if (!hasMore) return; + sendRecruitFetchRequest(); }} - hasMore={true} + hasMore={hasMore} loader={

    Loading...

    } > diff --git a/client/src/pages/SignUp.tsx b/client/src/pages/SignUp.tsx deleted file mode 100644 index a67da55..0000000 --- a/client/src/pages/SignUp.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { ChangeEvent, MouseEventHandler, useCallback, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; -import Header from "#components/Header/Header"; -import Input from "#components/Input/Input"; -import Button from "#components/Button/Button"; -import useInput from "#hooks/useInput"; -import { confirmPasswordValidator, idValidator, passwordValidator } from "#utils/valitationUtils"; -import { InputWrapper, LogoWrapper, OptionsWrapper } from "./SignUp.styles"; -import { PLACEHOLDER } from "#constants/placeholder"; -import usePaceInput from "#hooks/usePaceInput"; -import PaceInput from "#components/Input/PaceInput/PaceInput"; -import useHttpPost from "#hooks/http/useHttpPost"; -import useLocalAPI from "#hooks/useLocalAPI"; -import { LocalData, LocalSearchResponse } from "#types/Local"; -import ResetButton from "#components/ResetButton/ResetButton"; -import AddressList from "#components/AddressList/AddressList"; - -const SignUp = () => { - const [userId, onChangeUserId, userIdError] = useInput(idValidator); - const [password, onChangePassword, passwordError] = useInput(passwordValidator); - const [confirmPassword, onChangeConfirmPassword, confirmPasswordError] = useInput( - confirmPasswordValidator(String(password)), - ); - const query = useLocalAPI("/search/address.json"); - const [searchResult, setSearchResult] = useState([]); - const [hDong, setHDong] = useState({ name: "", code: "" }); - const { post } = useHttpPost(); - const { pace, onChangeMinute, onChangeSecond } = usePaceInput(); - const navigate = useNavigate(); - - const checkFormValidation = () => confirmPassword && password && userId && hDong.code; - - const onSubmitSignUp = async () => { - if (!checkFormValidation()) return; - try { - await post("/user", { userId, password, hCode: hDong.code, pace: pace.minute * 60 + pace.second }); - navigate("/", { replace: true }); - } catch (error: any) { - alert(error.message); - } - }; - - const onClickLocalData = useCallback( - (local: LocalData): MouseEventHandler => - () => { - setHDong({ name: local.address_name, code: local.address.h_code }); - setSearchResult([]); - }, - [], - ); - - const onChangeHName = (e: ChangeEvent) => setHDong((prev) => ({ ...prev, name: e.target.value })); - - const onClickResetButton = useCallback(() => { - setHDong({ name: "", code: "" }); - }, []); - - useEffect(() => { - if (hDong.code) return; - (async () => { - const result = await query({ analyze_type: "exact", size: 20, query: hDong.name }); - setSearchResult(result.documents.filter(isEupMyeonDong)); - })(); - }, [hDong]); - - const isEupMyeonDong = useCallback((local: LocalData) => { - return local.address_type === "REGION" && local.address.region_3depth_h_name; - }, []); - - return ( - <> -
    - RunWithMe - - - {userIdError} - - {passwordError} - - {confirmPasswordError} - -
    - 0} - subText={hDong.code.length > 0 && } - /> - {searchResult.length > 0 && } -
    - -
    - -
    - navigate("/pwInquiry")}>비밀번호 찾기 -
    -
    - navigate("/login")}>로그인 하기 -
    -
    - - ); -}; - -export default SignUp; diff --git a/client/src/pages/SignUp.styles.ts b/client/src/pages/SignUp/SignUp.styles.ts similarity index 87% rename from client/src/pages/SignUp.styles.ts rename to client/src/pages/SignUp/SignUp.styles.ts index c394411..f1aa9be 100644 --- a/client/src/pages/SignUp.styles.ts +++ b/client/src/pages/SignUp/SignUp.styles.ts @@ -1,14 +1,13 @@ import styled from "styled-components"; import { COLOR } from "styles/color"; -export const LogoWrapper = styled.div` +export const Logo = styled.div` color: ${COLOR.BLACK}; width: 100%; margin: 50px 0; height: 29px; font-size: 2.4rem; font-weight: bold; - font-family: "Noto Sans KR"; text-align: center; `; @@ -38,12 +37,12 @@ export const OptionsWrapper = styled.div` display: flex; justify-content: center; width: 100%; - div { + span { cursor: pointer; padding: 0 1rem; } - div:not(:last-child) { + span:not(:last-child) { border-right: 0.1rem solid grey; } `; diff --git a/client/src/pages/SignUp/SignUp.tsx b/client/src/pages/SignUp/SignUp.tsx new file mode 100644 index 0000000..2c9acd3 --- /dev/null +++ b/client/src/pages/SignUp/SignUp.tsx @@ -0,0 +1,71 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Header from "#components/Header/Header"; +import Input from "#components/Input/Input"; +import Button from "#components/Button/Button"; +import useInput from "#hooks/useInput"; +import { confirmPasswordValidator, idValidator, passwordValidator } from "#utils/validationUtils"; +import { InputWrapper, Logo, OptionsWrapper } from "./SignUp.styles"; +import { PLACEHOLDER } from "#constants/placeholder"; +import usePaceInput from "#hooks/usePaceInput"; +import PaceInput from "#components/Input/PaceInput/PaceInput"; +import useHttpPost from "#hooks/http/useHttpPost"; +import { Address } from "#types/Address"; +import AddressSearchInput from "#components/Input/AddressSearchInput/AddressSearchInput"; +import useAuth from "#hooks/useAuth"; + +const SignUp = () => { + useAuth(false); + const [userId, onChangeUserId, userIdError] = useInput(idValidator); + const [password, onChangePassword, passwordError] = useInput(passwordValidator); + const [region, setRegion] = useState
    (null); + const [confirmPassword, onChangeConfirmPassword, confirmPasswordError] = useInput( + confirmPasswordValidator(String(password)), + ); + const { post } = useHttpPost(); + const { pace, onChangeMinute, onChangeSecond } = usePaceInput(); + const navigate = useNavigate(); + + const checkFormValidation = () => confirmPassword && password && userId && region?.address.h_code; + + const onSubmitSignUp = async () => { + if (!checkFormValidation()) return; + const userInfo = { userId, password, hCode: region?.address.h_code, pace: pace.minute * 60 + pace.second }; + try { + await post("/user", userInfo); + navigate("/", { replace: true }); + } catch (error: any) { + alert(error.message); + } + }; + + return ( + <> +
    + RunWithMe + + + {userIdError} + + {passwordError} + + {confirmPasswordError} + + + + + + navigate("/pwInquiry")}>비밀번호 찾기 + navigate("/login")}>로그인 하기 + + + ); +}; + +export default SignUp; diff --git a/client/src/styles/flex.ts b/client/src/styles/flex.ts index 1a3cdce..21d4267 100644 --- a/client/src/styles/flex.ts +++ b/client/src/styles/flex.ts @@ -5,6 +5,11 @@ export const flexRowSpaceBetween = css` justify-content: space-between; `; +export const flexRowSpaceAround = css` + display: flex; + justify-content: space-around; +`; + export const flexRowCenter = css` display: flex; justify-content: center; diff --git a/client/src/types/Local.ts b/client/src/types/Address.ts similarity index 82% rename from client/src/types/Local.ts rename to client/src/types/Address.ts index bebe7e4..9084f77 100644 --- a/client/src/types/Local.ts +++ b/client/src/types/Address.ts @@ -1,8 +1,8 @@ -export interface LocalSearchResponse { - documents: LocalData[]; +export interface AddressSearchResponse { + documents: Address[]; } -export interface LocalData { +export interface Address { address: { address_name: string; b_code: string; diff --git a/client/src/types/FilterOption.ts b/client/src/types/FilterOption.ts new file mode 100644 index 0000000..85ff5b3 --- /dev/null +++ b/client/src/types/FilterOption.ts @@ -0,0 +1,5 @@ +export type FilterOption = { + text: string; + min: number; + max: number; +}; diff --git a/client/src/types/LocalAPIType.ts b/client/src/types/LocalAPIType.ts new file mode 100644 index 0000000..11e3644 --- /dev/null +++ b/client/src/types/LocalAPIType.ts @@ -0,0 +1,5 @@ +export const enum LOCAL_API_PATH { + ADDRESS = "/search/address.json", + REGION_CODE = "/geo/coord2regioncode.json", + PLACE = "/search/keyword.json", +} diff --git a/client/src/types/MapControlProps.ts b/client/src/types/MapControlProps.ts index ab3d6c2..82dcf42 100644 --- a/client/src/types/MapControlProps.ts +++ b/client/src/types/MapControlProps.ts @@ -1,8 +1,8 @@ export interface MapControlProps { - position: MapControlPotition; + position: MapControlPosition; } -export interface MapControlPotition { +export interface MapControlPosition { top?: string; left?: string; bottom?: string; diff --git a/client/src/types/maps/LatLngBounds.d.ts b/client/src/types/maps/LatLngBounds.d.ts new file mode 100644 index 0000000..373b6a4 --- /dev/null +++ b/client/src/types/maps/LatLngBounds.d.ts @@ -0,0 +1,5 @@ +declare namespace kakao.maps { + export class LatLngBounds { + constructor(sw: LatLng, ne: LatLng); + } +} diff --git a/client/src/utils/addressUtils.ts b/client/src/utils/addressUtils.ts new file mode 100644 index 0000000..6fd983c --- /dev/null +++ b/client/src/utils/addressUtils.ts @@ -0,0 +1,4 @@ +import { Address } from "#types/Address"; + +const isEupMyeonDong = (local: Address) => local.address_type === "REGION" && local.address.region_3depth_h_name; +export { isEupMyeonDong }; diff --git a/client/src/utils/paceUtils.ts b/client/src/utils/paceUtils.ts new file mode 100644 index 0000000..f79a064 --- /dev/null +++ b/client/src/utils/paceUtils.ts @@ -0,0 +1,3 @@ +export const getPaceFormat = (sec: number): string => { + return `${parseInt(String(sec / 60))}'${sec % 60}"`; +}; diff --git a/client/src/utils/pathUtils.ts b/client/src/utils/pathUtils.ts new file mode 100644 index 0000000..92c04aa --- /dev/null +++ b/client/src/utils/pathUtils.ts @@ -0,0 +1,33 @@ +export const getMiddlePoint = (path: { lat: number; lng: number }[]) => { + const bounds = getBounds(path); + return { lat: (bounds.minLat + bounds.maxLat) / 2, lng: (bounds.minLng + bounds.maxLng) / 2 }; +}; + +export const getBounds = (path: { lat: number; lng: number }[]) => { + let minLat = 90; + let maxLat = -90; + let minLng = 180; + let maxLng = -180; + + for (const point of path) { + if (minLat > point.lat) { + minLat = point.lat; + } + if (maxLat < point.lat) { + maxLat = point.lat; + } + if (minLng > point.lng) { + minLng = point.lng; + } + if (maxLng < point.lng) { + maxLng = point.lng; + } + } + + return { + minLat, + maxLat, + minLng, + maxLng, + }; +}; diff --git a/client/src/utils/valitationUtils.ts b/client/src/utils/validationUtils.ts similarity index 83% rename from client/src/utils/valitationUtils.ts rename to client/src/utils/validationUtils.ts index 2ada06d..58dc2d8 100644 --- a/client/src/utils/valitationUtils.ts +++ b/client/src/utils/validationUtils.ts @@ -18,5 +18,9 @@ export const hNameValidator = (hCode: string) => { }; export const courseTitleValidator = (title: string) => { - return !title ? "제목을 입력하세요" : ""; + return !title.trim() ? "제목을 입력하세요" : ""; +}; + +export const recruitTitleValidator = (title: string) => { + return !title.trim() ? "제목을 입력하세요" : ""; }; diff --git a/dump.rdb b/dump.rdb deleted file mode 100644 index 6bba99ddb702ffcbd2f7d7960e9c064fd947265e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 456 zcmbV{&rZTX5QiHR6HsHK2X9`ud7x?eBXEPzVzyyhDYa}pS(b%vODRyG-CjJK_!7Q= z&p>zxpTTFSi3%ZxyOYdeX1+|mpDs(FD+r>HX6Ix;!~BZOk1M>um(!E|qE8p$x}F~6 z1@+11>f;%wnPo@@d7t>3r_T#6&Dg;(-?u}u=DxYFBaX{rdqF1loQd`K9AS#nzr$3c z3V29>s8SGuiC)G{a0|RCGMh?`-$EiTn5^6D>_FE-`nd167%^npZvNf$5^#ffJ5MdN z({ isGlobal: true, @@ -43,12 +46,18 @@ import { CourseModule } from "./course/course.module"; ServeStaticModule.forRoot({ rootPath: join(__dirname, "..", "..", "client", "build"), }), + HttpRequestBodyModule, + CustomJwtModule, UserModule, AuthModule, RecruitModule, CourseModule, ], - controllers: [AppController], - providers: [AppService], + providers: [ + { + provide: APP_INTERCEPTOR, + useClass: HttpRequestBodyInterceptor, + }, + ], }) export class AppModule {} diff --git a/server/src/app.service.ts b/server/src/app.service.ts deleted file mode 100644 index fbf450f..0000000 --- a/server/src/app.service.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Injectable } from "@nestjs/common"; - -@Injectable() -export class AppService {} diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index 520ac2e..e88c25a 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -3,8 +3,8 @@ import { Request, Response } from "express"; import { AuthService } from "./auth.service"; import { LoginUserDto } from "./dto/login-user.dto"; import { plainToClass } from "class-transformer"; -import { AccessGuard } from "src/common/guard/access.guard"; -import { RefreshGuard } from "src/common/guard/refresh.guard"; +import { AccessGuard } from "src/common/guards/access.guard"; +import { RefreshGuard } from "src/common/guards/refresh.guard"; @Controller("auth") export class AuthController { diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts index 75be6b5..ba32daa 100644 --- a/server/src/auth/auth.module.ts +++ b/server/src/auth/auth.module.ts @@ -1,17 +1,14 @@ import { Module } from "@nestjs/common"; +import { AuthRepository } from "src/common/repositories/auth.repository"; import { AuthService } from "./auth.service"; import { AuthController } from "./auth.controller"; -import { UserService } from "src/user/user.service"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { User } from "src/entities/user.entity"; -import { JwtService } from "@nestjs/jwt"; import { TypeOrmCustomModule } from "src/common/typeorm/typeorm.module"; -import { UserRepository } from "src/user/user.repository"; -import { AuthRepository } from "./auth.repository"; +import { UserRepository } from "src/common/repositories/user.repository"; +import { CustomJwtModule } from "src/common/modules/custom-jwt/custom-jwt.module"; @Module({ - imports: [TypeOrmModule.forFeature([User]), TypeOrmCustomModule.forCustomRepository([UserRepository])], - providers: [AuthService, UserService, JwtService, AuthRepository], + imports: [TypeOrmCustomModule.forCustomRepository([UserRepository]), CustomJwtModule], + providers: [AuthService, AuthRepository], controllers: [AuthController], }) export class AuthModule {} diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index c6dcb8e..d6a4ed2 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,15 +1,15 @@ import { Injectable, UnauthorizedException } from "@nestjs/common"; import { LoginUserDto } from "./dto/login-user.dto"; import * as bcrypt from "bcryptjs"; -import { JwtService } from "@nestjs/jwt"; -import { UserRepository } from "src/user/user.repository"; -import { AuthRepository } from "./auth.repository"; +import { UserRepository } from "src/common/repositories/user.repository"; +import { AuthRepository } from "../common/repositories/auth.repository"; +import { CustomJwtService } from "src/common/modules/custom-jwt/custom-jwt.service"; @Injectable() export class AuthService { constructor( + private jwtService: CustomJwtService, private userRepository: UserRepository, - private jwtService: JwtService, private authRepository: AuthRepository, ) {} @@ -30,40 +30,27 @@ export class AuthService { async logoutUser(userId: string) { this.authRepository.deleteToken(userId); } + async getAccessToken(userId: string) { - const token = this.jwtService.sign( - { userId, userIdx: await this.userRepository.findUserIdxByUserId(userId) }, - { - secret: process.env.ACCESS_SECRET, - expiresIn: "15m", - }, - ); + const userIdx = await this.userRepository.findUserIdxByUserId(userId); + const token = await this.jwtService.createAccessToken(userId, userIdx); return token; } async getRefreshToken(userId: string) { - const token = this.jwtService.sign( - { userId, userIdx: await this.userRepository.findUserIdxByUserId(userId) }, - { - secret: process.env.REFRESH_SECRET, - expiresIn: "30d", - }, - ); + const userIdx = await this.userRepository.findUserIdxByUserId(userId); + const token = await this.jwtService.createRefreshToken(userId, userIdx); this.authRepository.saveToken(token, userId); return token; } verifyRefreshToken(jwtString: string) { - const payload = this.jwtService.verify(jwtString, { secret: process.env.REFRESH_SECRET }); - const { userId } = payload; - const { userIdx } = payload; - return { userId, userIdx }; + const payload = this.jwtService.verifyRefreshToken(jwtString); + return payload; } verifyAccessToken(jwtString: string) { - const payload = this.jwtService.verify(jwtString, { secret: process.env.ACCESS_SECRET }); - const { userId } = payload; - const { userIdx } = payload; - return { userId, userIdx }; + const payload = this.jwtService.verifyAccessToken(jwtString); + return payload; } } diff --git a/server/src/auth/dto/login-user.dto.ts b/server/src/auth/dto/login-user.dto.ts index e7732f9..69da3b8 100644 --- a/server/src/auth/dto/login-user.dto.ts +++ b/server/src/auth/dto/login-user.dto.ts @@ -1,4 +1,4 @@ -import { IsValidId, IsValidPassword } from "src/common/decorator"; +import { IsValidId, IsValidPassword } from "src/common/decorators"; export class LoginUserDto { @IsValidId() diff --git a/server/src/common/constant/error_message.ts b/server/src/common/constants/error_message.ts similarity index 100% rename from server/src/common/constant/error_message.ts rename to server/src/common/constants/error_message.ts diff --git a/server/src/common/decorator/date.validator.ts b/server/src/common/decorators/date.validator.ts similarity index 100% rename from server/src/common/decorator/date.validator.ts rename to server/src/common/decorators/date.validator.ts diff --git a/server/src/common/decorator/id.validator.ts b/server/src/common/decorators/id.validator.ts similarity index 100% rename from server/src/common/decorator/id.validator.ts rename to server/src/common/decorators/id.validator.ts diff --git a/server/src/common/decorator/index.ts b/server/src/common/decorators/index.ts similarity index 100% rename from server/src/common/decorator/index.ts rename to server/src/common/decorators/index.ts diff --git a/server/src/common/decorator/path.validator.ts b/server/src/common/decorators/path.validator.ts similarity index 95% rename from server/src/common/decorator/path.validator.ts rename to server/src/common/decorators/path.validator.ts index 4357a7f..29dbc2e 100644 --- a/server/src/common/decorator/path.validator.ts +++ b/server/src/common/decorators/path.validator.ts @@ -4,7 +4,7 @@ import { ValidatorConstraint, ValidatorConstraintInterface, } from "class-validator"; -import { LatLng } from "../type/lat-lng"; +import { LatLng } from "../types/lat-lng"; @ValidatorConstraint({ name: "isValidPath", async: false }) class isValidPathConstraint implements ValidatorConstraintInterface { diff --git a/server/src/common/decorator/pw.validator.ts b/server/src/common/decorators/pw.validator.ts similarity index 100% rename from server/src/common/decorator/pw.validator.ts rename to server/src/common/decorators/pw.validator.ts diff --git a/server/src/entities/course.entity.ts b/server/src/common/entities/course.entity.ts similarity index 73% rename from server/src/entities/course.entity.ts rename to server/src/common/entities/course.entity.ts index f5c7445..75eb87a 100644 --- a/server/src/entities/course.entity.ts +++ b/server/src/common/entities/course.entity.ts @@ -1,6 +1,7 @@ -import { LatLng } from "src/common/type/lat-lng"; -import { User } from "src/entities/user.entity"; +import { LatLng } from "src/common/types/lat-lng"; +import { User } from "src/common/entities/user.entity"; import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { HDong } from "./h_dong.entity"; import { Recruit } from "./recruit.entity"; @Entity("course") @@ -20,12 +21,11 @@ export class Course { @Column() pathLength: number; - @Column({ type: "varchar", length: 10, nullable: true }) - name: string; - @CreateDateColumn() createdAt: Date; + @ManyToOne(() => HDong, (hCode) => hCode.courses, { nullable: true }) + @JoinColumn({ name: "hCode", referencedColumnName: "code" }) @Column({ type: "varchar", length: 10 }) hCode: string; @@ -39,15 +39,7 @@ export class Course { @JoinColumn({ name: "userId", referencedColumnName: "id" }) user: User; - static of( - title: string, - img: string, - path: LatLng[], - pathLength: number, - hCode: string, - userId: number, - name: string, - ) { + static of(title: string, img: string, path: LatLng[], pathLength: number, hCode: string, userId: number) { const course = new Course(); course.title = title; course.img = img; @@ -55,7 +47,6 @@ export class Course { course.hCode = hCode; course.pathLength = pathLength; course.userId = userId; - course.name = name; return course; } } diff --git a/server/src/common/entities/h_dong.entity.ts b/server/src/common/entities/h_dong.entity.ts new file mode 100644 index 0000000..6cdccbb --- /dev/null +++ b/server/src/common/entities/h_dong.entity.ts @@ -0,0 +1,14 @@ +import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm"; +import { Course } from "src/common/entities/course.entity"; + +@Entity("h_dong") +export class HDong { + @PrimaryColumn() + code: string; + + @Column() + name: string; + + @OneToMany(() => Course, (course) => course.hCode) + courses: Course[]; +} diff --git a/server/src/entities/recruit.entity.ts b/server/src/common/entities/recruit.entity.ts similarity index 80% rename from server/src/entities/recruit.entity.ts rename to server/src/common/entities/recruit.entity.ts index a335ea8..c21a0da 100644 --- a/server/src/entities/recruit.entity.ts +++ b/server/src/common/entities/recruit.entity.ts @@ -1,4 +1,4 @@ -import { User } from "src/entities/user.entity"; +import { User } from "src/common/entities/user.entity"; import { Column, CreateDateColumn, @@ -29,9 +29,6 @@ export class Recruit { @Column() pace: number; - @Column({ type: "varchar", length: 10 }) - name: string; - @CreateDateColumn() createdAt: Date; @@ -55,21 +52,12 @@ export class Recruit { @JoinColumn({ name: "userId", referencedColumnName: "id" }) user: User; - static of( - title: string, - startTime: Date, - maxPpl: number, - pace: number, - hCode: string, - userId: number, - courseId: number, - ) { + static of(title: string, startTime: Date, maxPpl: number, pace: number, userId: number, courseId: number) { const recruit = new Recruit(); recruit.title = title; recruit.startTime = startTime; recruit.maxPpl = maxPpl; recruit.pace = pace; - recruit.name = hCode; recruit.userId = userId; recruit.courseId = courseId; return recruit; diff --git a/server/src/entities/user.entity.ts b/server/src/common/entities/user.entity.ts similarity index 88% rename from server/src/entities/user.entity.ts rename to server/src/common/entities/user.entity.ts index a21ff97..6fe7307 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/common/entities/user.entity.ts @@ -1,5 +1,5 @@ -import { Course } from "src/entities/course.entity"; -import { Recruit } from "src/entities/recruit.entity"; +import { Course } from "src/common/entities/course.entity"; +import { Recruit } from "src/common/entities/recruit.entity"; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { UserRecruit } from "./user_recruit.entity"; diff --git a/server/src/entities/user_recruit.entity.ts b/server/src/common/entities/user_recruit.entity.ts similarity index 100% rename from server/src/entities/user_recruit.entity.ts rename to server/src/common/entities/user_recruit.entity.ts diff --git a/server/src/common/guard/access.guard.ts b/server/src/common/guards/access.guard.ts similarity index 76% rename from server/src/common/guard/access.guard.ts rename to server/src/common/guards/access.guard.ts index 4fec85b..1029ef4 100644 --- a/server/src/common/guard/access.guard.ts +++ b/server/src/common/guards/access.guard.ts @@ -1,11 +1,11 @@ import { Request } from "express"; import { Observable } from "rxjs"; import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; -import { AuthService } from "src/auth/auth.service"; +import { CustomJwtService } from "src/common/modules/custom-jwt/custom-jwt.service"; @Injectable() export class AccessGuard implements CanActivate { - constructor(private authService: AuthService) {} + constructor(private jwtService: CustomJwtService) {} canActivate(context: ExecutionContext): boolean | Promise | Observable { const request = context.switchToHttp().getRequest(); @@ -18,7 +18,7 @@ export class AccessGuard implements CanActivate { return false; } - this.authService.verifyAccessToken(jwtString); + this.jwtService.verifyAccessToken(jwtString); return true; } } diff --git a/server/src/common/guard/refresh.guard.ts b/server/src/common/guards/refresh.guard.ts similarity index 75% rename from server/src/common/guard/refresh.guard.ts rename to server/src/common/guards/refresh.guard.ts index 922dc47..91d4be2 100644 --- a/server/src/common/guard/refresh.guard.ts +++ b/server/src/common/guards/refresh.guard.ts @@ -1,11 +1,11 @@ import { Request } from "express"; import { Observable } from "rxjs"; import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; -import { AuthService } from "src/auth/auth.service"; +import { CustomJwtService } from "src/common/modules/custom-jwt/custom-jwt.service"; @Injectable() export class RefreshGuard implements CanActivate { - constructor(private authService: AuthService) {} + constructor(private jwtService: CustomJwtService) {} canActivate(context: ExecutionContext): boolean | Promise | Observable { const request = context.switchToHttp().getRequest(); @@ -17,7 +17,7 @@ export class RefreshGuard implements CanActivate { if (!jwtString) { return false; } - this.authService.verifyRefreshToken(jwtString); + this.jwtService.verifyRefreshToken(jwtString); return true; } } diff --git a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts new file mode 100644 index 0000000..5225fbe --- /dev/null +++ b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts @@ -0,0 +1,22 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; +import { Observable } from "rxjs"; +import { CustomJwtService } from "src/common/modules/custom-jwt/custom-jwt.service"; + +@Injectable() +export class HttpRequestBodyInterceptor implements NestInterceptor { + constructor(private jwtService: CustomJwtService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const accessToken = request.headers["authorization"]; + + if (accessToken) { + try { + const { userIdx } = this.jwtService.verifyAccessToken(accessToken); + request.body.userId = userIdx; + } catch (err) {} + } + + return next.handle(); + } +} diff --git a/server/src/common/interceptors/http-request/http-request-body.module.ts b/server/src/common/interceptors/http-request/http-request-body.module.ts new file mode 100644 index 0000000..2fea6db --- /dev/null +++ b/server/src/common/interceptors/http-request/http-request-body.module.ts @@ -0,0 +1,10 @@ +import { Module } from "@nestjs/common"; +import { APP_INTERCEPTOR } from "@nestjs/core"; +import { CustomJwtModule } from "src/common/modules/custom-jwt/custom-jwt.module"; +import { HttpRequestBodyInterceptor } from "./http-request-body.interceptor"; + +@Module({ + imports: [CustomJwtModule], + providers: [{ provide: APP_INTERCEPTOR, useClass: HttpRequestBodyInterceptor }], +}) +export class HttpRequestBodyModule {} diff --git a/server/src/common/modules/custom-jwt/custom-jwt.module.ts b/server/src/common/modules/custom-jwt/custom-jwt.module.ts new file mode 100644 index 0000000..8eb9447 --- /dev/null +++ b/server/src/common/modules/custom-jwt/custom-jwt.module.ts @@ -0,0 +1,11 @@ +import { Global, Module } from "@nestjs/common"; +import { JwtModule } from "@nestjs/jwt"; +import { CustomJwtService } from "./custom-jwt.service"; + +@Global() +@Module({ + imports: [JwtModule.register({})], + providers: [CustomJwtService], + exports: [CustomJwtService], +}) +export class CustomJwtModule {} diff --git a/server/src/common/modules/custom-jwt/custom-jwt.service.ts b/server/src/common/modules/custom-jwt/custom-jwt.service.ts new file mode 100644 index 0000000..8306cec --- /dev/null +++ b/server/src/common/modules/custom-jwt/custom-jwt.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from "@nestjs/common"; +import { JwtService } from "@nestjs/jwt"; + +@Injectable() +export class CustomJwtService { + constructor(private jwtService: JwtService) {} + + async createAccessToken(userId: string, userIdx: number) { + const token = this.jwtService.sign( + { userId, userIdx }, + { + secret: process.env.ACCESS_SECRET, + expiresIn: "15m", + }, + ); + return token; + } + + async createRefreshToken(userId: string, userIdx: number) { + const token = this.jwtService.sign( + { userId, userIdx }, + { + secret: process.env.REFRESH_SECRET, + expiresIn: "30d", + }, + ); + return token; + } + + verifyRefreshToken(jwtString: string) { + const payload = this.jwtService.verify(jwtString, { secret: process.env.REFRESH_SECRET }); + const { userId, userIdx } = payload; + return { userId, userIdx }; + } + + verifyAccessToken(jwtString: string) { + const payload = this.jwtService.verify(jwtString, { secret: process.env.ACCESS_SECRET }); + const { userId, userIdx } = payload; + return { userId, userIdx }; + } +} diff --git a/server/src/auth/auth.repository.ts b/server/src/common/repositories/auth.repository.ts similarity index 100% rename from server/src/auth/auth.repository.ts rename to server/src/common/repositories/auth.repository.ts diff --git a/server/src/course/course.repository.ts b/server/src/common/repositories/course.repository.ts similarity index 67% rename from server/src/course/course.repository.ts rename to server/src/common/repositories/course.repository.ts index c4d39ff..5a9a9ab 100644 --- a/server/src/course/course.repository.ts +++ b/server/src/common/repositories/course.repository.ts @@ -1,7 +1,7 @@ import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; import { Repository } from "typeorm"; -import { Course } from "src/entities/course.entity"; -import { CourseData } from "src/common/type/raw-course-data"; +import { Course } from "src/common/entities/course.entity"; +import { BadRequestException } from "@nestjs/common"; @CustomRepository(Course) export class CourseRepository extends Repository { @@ -9,6 +9,15 @@ export class CourseRepository extends Repository { return this.save(courseEntity); } + async findCourseDetail(courseId: number) { + return this.createQueryBuilder("course") + .innerJoinAndSelect("course.hCode", "h_dong") + .innerJoinAndSelect("course.user", "user") + .select(["course.title", "course.path", "course.pathLength", "user.userId", "h_dong.name"]) + .where("course.id = :courseId", { courseId }) + .getOne(); + } + async findAll( page: number, pageSize: number, @@ -17,9 +26,10 @@ export class CourseRepository extends Repository { author?: boolean | undefined, minLen?: number | undefined, maxLen?: number | undefined, - ): Promise { + ) { return this.createQueryBuilder("course") .innerJoinAndSelect("course.user", "user") + .innerJoinAndSelect("course.hCode", "h_dong") .where("1=1") .andWhere(maxLen && minLen >= 0 ? `course.pathLength >= :minLen and course.pathLength < :maxLen` : "1=1", { minLen, @@ -41,8 +51,7 @@ export class CourseRepository extends Repository { "course.img", "course.path", "course.pathLength", - "course.hCode", - "course.name", + "h_dong.name", "course.createdAt", "user.userId", ]) @@ -50,4 +59,11 @@ export class CourseRepository extends Repository { .limit(pageSize) .getMany(); } + async findOneById(id: number): Promise { + const data = await this.findOneBy({ id }); + if (!data) { + throw new BadRequestException(); + } + return data; + } } diff --git a/server/src/common/repository/h_dong.repository.ts b/server/src/common/repositories/h_dong.repository.ts similarity index 76% rename from server/src/common/repository/h_dong.repository.ts rename to server/src/common/repositories/h_dong.repository.ts index ee92c1d..f89fcec 100644 --- a/server/src/common/repository/h_dong.repository.ts +++ b/server/src/common/repositories/h_dong.repository.ts @@ -1,5 +1,5 @@ import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; -import { HDong } from "src/entities/h_dong.entity"; +import { HDong } from "src/common/entities/h_dong.entity"; import { Repository } from "typeorm"; @CustomRepository(HDong) diff --git a/server/src/recruit/recruit.repository.ts b/server/src/common/repositories/recruit.repository.ts similarity index 88% rename from server/src/recruit/recruit.repository.ts rename to server/src/common/repositories/recruit.repository.ts index bedd69e..bd12f5d 100644 --- a/server/src/recruit/recruit.repository.ts +++ b/server/src/common/repositories/recruit.repository.ts @@ -1,7 +1,7 @@ import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; -import { Recruit } from "src/entities/recruit.entity"; +import { Recruit } from "src/common/entities/recruit.entity"; import { Repository } from "typeorm"; -import { RawRecruitData } from "src/common/type/raw-recruit-data"; +import { RawRecruitData } from "src/common/types/raw-recruit-data"; import { BadRequestException } from "@nestjs/common"; @CustomRepository(Recruit) @@ -11,23 +11,23 @@ export class RecruitRepository extends Repository { } async findRecruitDetail(recruitId: number) { - await this.findOneById(recruitId); return this.createQueryBuilder("recruit") .innerJoinAndSelect("recruit.course", "course") + .innerJoinAndSelect("course.hCode", "h_dong") .leftJoinAndSelect("recruit.userRecruits", "user_recruit") .innerJoinAndSelect("recruit.user", "user") .select([ "recruit.title AS title", - "STR_TO_DATE(recruit.startTime) AS startTime", - "recruit.name AS name", "recruit.maxPpl AS maxPpl", "recruit.pace AS pace", "recruit.userId AS authorId", + "recruit.startTime AS startTime", "user.userId AS userId", "COUNT(user_recruit.userId) AS currentPpl", "course.path AS path", "course.pathLength AS pathLength", "user.userId AS userId", + "h_dong.name", ]) .where("recruit.id = :recruitId", { recruitId }) .getRawOne(); @@ -46,6 +46,7 @@ export class RecruitRepository extends Repository { return this.createQueryBuilder("recruit") .innerJoinAndSelect("recruit.course", "course") .innerJoinAndSelect("course.user", "u") + .innerJoinAndSelect("course.hCode", "h_dong") .leftJoinAndSelect("recruit.userRecruits", "user_recruit") .innerJoinAndSelect("recruit.user", "user") .where("recruit.startTime > NOW()") @@ -78,8 +79,7 @@ export class RecruitRepository extends Repository { "course.img", "course.path", "course.pathLength", - "course.hCode", - "course.name", + "h_dong.name", "course.createdAt", "u.userId AS course_userId", ]) @@ -90,11 +90,7 @@ export class RecruitRepository extends Repository { } async findOneById(id: number): Promise { - const data = await this.findOneBy({ id }); - if (!data) { - throw new BadRequestException(); - } - return data; + return await this.findOneBy({ id }); } async getMaxPpl(id: number) { diff --git a/server/src/user/user.repository.ts b/server/src/common/repositories/user.repository.ts similarity index 89% rename from server/src/user/user.repository.ts rename to server/src/common/repositories/user.repository.ts index 7f33d66..e409619 100644 --- a/server/src/user/user.repository.ts +++ b/server/src/common/repositories/user.repository.ts @@ -1,5 +1,5 @@ import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; -import { User } from "src/entities/user.entity"; +import { User } from "src/common/entities/user.entity"; import { Repository } from "typeorm"; @CustomRepository(User) diff --git a/server/src/user_recruit.repository.ts b/server/src/common/repositories/user_recruit.repository.ts similarity index 93% rename from server/src/user_recruit.repository.ts rename to server/src/common/repositories/user_recruit.repository.ts index 1f9a81b..142deb6 100644 --- a/server/src/user_recruit.repository.ts +++ b/server/src/common/repositories/user_recruit.repository.ts @@ -1,6 +1,6 @@ import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; import { Repository } from "typeorm"; -import { UserRecruit } from "./entities/user_recruit.entity"; +import { UserRecruit } from "../entities/user_recruit.entity"; @CustomRepository(UserRecruit) export class UserRecruitRepository extends Repository { diff --git a/server/src/common/repository/user_recruit.repository.ts b/server/src/common/repository/user_recruit.repository.ts deleted file mode 100644 index 965896a..0000000 --- a/server/src/common/repository/user_recruit.repository.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CustomRepository } from "src/common/typeorm/typeorm.decorator"; -import { UserRecruit } from "src/entities/user_recruit.entity"; -import { Repository } from "typeorm"; - -@CustomRepository(UserRecruit) -export class UserRecruitRepository extends Repository { - public async isParticipate(recruitId: number, userId: number): Promise { - const participants = await this.createQueryBuilder("user_recruit") - .select("userId") - .where("user_recruit.recruitId = :recruitId", { recruitId }) - .andWhere("user_recruit.userId = :userId", { userId }) - .execute(); - if (participants.length !== 0) { - return true; - } - return false; - } - public async countCurrentPpl(recruitId: number) { - return await this.countBy({ recruitId }); - } - - public async getAttendeeCntQb() { - const attendeeCntQb = this.createQueryBuilder() - .subQuery() - .select(["COUNT(userRecruit.userId) AS currentPpl", "userRecruit.recruitId AS recruitId"]) - .groupBy("userRecruit.recruitId") - .getQuery(); - - return attendeeCntQb; - } -} diff --git a/server/src/common/type/raw-course-data.ts b/server/src/common/types/course-data.ts similarity index 100% rename from server/src/common/type/raw-course-data.ts rename to server/src/common/types/course-data.ts diff --git a/server/src/common/type/lat-lng.ts b/server/src/common/types/lat-lng.ts similarity index 100% rename from server/src/common/type/lat-lng.ts rename to server/src/common/types/lat-lng.ts diff --git a/server/src/common/type/raw-recruit-data.ts b/server/src/common/types/raw-recruit-data.ts similarity index 93% rename from server/src/common/type/raw-recruit-data.ts rename to server/src/common/types/raw-recruit-data.ts index 1015240..e50424b 100644 --- a/server/src/common/type/raw-recruit-data.ts +++ b/server/src/common/types/raw-recruit-data.ts @@ -4,7 +4,7 @@ export interface RawRecruitData { course_img: string; course_path: string; course_pathLength: number; - course_name: string; + h_dong_name: string; course_createdAt: Date; course_userId: string; id: number; diff --git a/server/src/common/utils/plainToGetRecruitDto.ts b/server/src/common/utils/plainToGetRecruitDto.ts index 103a83b..5e4dce8 100644 --- a/server/src/common/utils/plainToGetRecruitDto.ts +++ b/server/src/common/utils/plainToGetRecruitDto.ts @@ -1,4 +1,4 @@ -import { RawRecruitData } from "../type/raw-recruit-data"; +import { RawRecruitData } from "../types/raw-recruit-data"; export const plainToGetRecruitDto = (plainRecruitData: RawRecruitData) => { const { @@ -15,7 +15,7 @@ export const plainToGetRecruitDto = (plainRecruitData: RawRecruitData) => { course_img, course_path, course_pathLength, - course_name, + h_dong_name, course_createdAt, course_userId, } = plainRecruitData; @@ -33,11 +33,11 @@ export const plainToGetRecruitDto = (plainRecruitData: RawRecruitData) => { id: course_id, title: course_title, img: course_img, - path: course_path, + path: JSON.parse(course_path), pathLength: course_pathLength, userId: course_userId, hDong: { - name: course_name, + name: h_dong_name, }, createdAt: course_createdAt, }, diff --git a/server/src/course/course.controller.ts b/server/src/course/course.controller.ts index 9f00a81..4d38af1 100644 --- a/server/src/course/course.controller.ts +++ b/server/src/course/course.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Get, Post, Query } from "@nestjs/common"; +import { Body, Controller, Get, NotFoundException, Param, Post, Query } from "@nestjs/common"; import { CourseService } from "./course.service"; import { CreateCourseDto } from "./dto/create-course.dto"; import { GetCourseDto } from "./dto/get-course.dto"; @@ -19,11 +19,20 @@ export class CourseController { } @Get() - async getCourses(@Query() queryParams: GetCourseDto) { - const courseList = await this.courseService.getCourseList(queryParams); + async getMany(@Query() queryParams: GetCourseDto) { + const courseList = await this.courseService.getMany(queryParams); return { statusCode: 200, data: courseList, }; } + + @Get(":id") + async getOne(@Param("id") courseId: number) { + if (!(await this.courseService.isExistingCourse(courseId))) { + throw new NotFoundException("Does not exist or has been deleted"); + } + const data = await this.courseService.getOne(courseId); + return data; + } } diff --git a/server/src/course/course.module.ts b/server/src/course/course.module.ts index eafe61f..cd8b978 100644 --- a/server/src/course/course.module.ts +++ b/server/src/course/course.module.ts @@ -3,8 +3,8 @@ import { CourseService } from "./course.service"; import { CourseController } from "./course.controller"; import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmCustomModule } from "src/common/typeorm/typeorm.module"; -import { Course } from "src/entities/course.entity"; -import { CourseRepository } from "./course.repository"; +import { Course } from "src/common/entities/course.entity"; +import { CourseRepository } from "../common/repositories/course.repository"; @Module({ imports: [TypeOrmModule.forFeature([Course]), TypeOrmCustomModule.forCustomRepository([CourseRepository])], diff --git a/server/src/course/course.service.ts b/server/src/course/course.service.ts index dade6de..03a08a0 100644 --- a/server/src/course/course.service.ts +++ b/server/src/course/course.service.ts @@ -1,6 +1,7 @@ import { Injectable } from "@nestjs/common"; -import { Course } from "src/entities/course.entity"; -import { CourseRepository } from "./course.repository"; +import { Course } from "src/common/entities/course.entity"; +import { HDong } from "src/common/entities/h_dong.entity"; +import { CourseRepository } from "../common/repositories/course.repository"; import { CreateCourseDto } from "./dto/create-course.dto"; import { GetCourseDto } from "./dto/get-course.dto"; @@ -13,7 +14,7 @@ export class CourseService { return this.courseRepository.createOne(courseEntity); } - async getCourseList(queryParams: GetCourseDto) { + async getMany(queryParams: GetCourseDto) { if (queryParams.getQuery() === "") { return []; } @@ -32,19 +33,31 @@ export class CourseService { queryParams.getMaxLength(), ); - return courseList.map(({ id, title, img, path, pathLength, name, createdAt, user }) => { + return courseList.map(({ id, title, img, path, pathLength, createdAt, user, hCode }) => { return { id, title, img, - path, + path: JSON.parse(path), pathLength, - hDong: { - name, - }, + hDong: hCode, createdAt, user, }; }); } + + async getOne(recruitId: number) { + const data = await this.courseRepository.findCourseDetail(recruitId); + const { title, path, pathLength } = data; + return { title, path, pathLength, hDong: data.hCode, userId: data.user.userId }; + } + + async isExistingCourse(recruitId: number): Promise { + const courseEntity = await this.courseRepository.findOneById(recruitId); + if (courseEntity) { + return true; + } + return false; + } } diff --git a/server/src/course/dto/create-course.dto.ts b/server/src/course/dto/create-course.dto.ts index 60e5e87..657cd4e 100644 --- a/server/src/course/dto/create-course.dto.ts +++ b/server/src/course/dto/create-course.dto.ts @@ -1,7 +1,7 @@ import { IsNumber, IsNumberString, IsString } from "class-validator"; -import { isValidPath } from "src/common/decorator/path.validator"; -import { LatLng } from "src/common/type/lat-lng"; -import { Course } from "src/entities/course.entity"; +import { isValidPath } from "src/common/decorators/path.validator"; +import { LatLng } from "src/common/types/lat-lng"; +import { Course } from "src/common/entities/course.entity"; export class CreateCourseDto { @IsString() @@ -19,13 +19,10 @@ export class CreateCourseDto { @IsNumber() private userId: number; - @IsString() - private name: string; - @IsNumberString() private hCode: string; toEntity() { - return Course.of(this.title, this.img, this.path, this.pathLength, this.hCode, this.userId, this.name); + return Course.of(this.title, this.img, this.path, this.pathLength, this.hCode, this.userId); } } diff --git a/server/src/entities/h_dong.entity.ts b/server/src/entities/h_dong.entity.ts deleted file mode 100644 index 2b68d5f..0000000 --- a/server/src/entities/h_dong.entity.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; - -@Entity("h_dong") -export class HDong { - @PrimaryGeneratedColumn() - id: number; - - @Column() - code: string; - - @Column() - name: string; -} diff --git a/server/src/main.ts b/server/src/main.ts index 32879d4..242b5ca 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -13,7 +13,11 @@ async function bootstrap() { .addTag("app") .build(); const document = SwaggerModule.createDocument(app, options); - app.enableCors({ origin: true, methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", credentials: true }); + app.enableCors({ + origin: "http://localhost:3000", + methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", + credentials: true, + }); app.use(cookieParser()); app.useGlobalPipes( new ValidationPipe({ diff --git a/server/src/recruit/dto/create-recruit.dto.ts b/server/src/recruit/dto/request/create-recruit.request.ts similarity index 54% rename from server/src/recruit/dto/create-recruit.dto.ts rename to server/src/recruit/dto/request/create-recruit.request.ts index 6031c2e..5dfcb54 100644 --- a/server/src/recruit/dto/create-recruit.dto.ts +++ b/server/src/recruit/dto/request/create-recruit.request.ts @@ -1,13 +1,12 @@ import { Type } from "class-transformer"; -import { IsNumber, IsNumberString, IsString } from "class-validator"; -import { IsValidDateTime } from "src/common/decorator"; -import { Recruit } from "../../entities/recruit.entity"; +import { IsNumber, IsString } from "class-validator"; +import { Recruit } from "../../../common/entities/recruit.entity"; export class CreateRecruitDto { @IsString() private title: string; - @IsValidDateTime() + @Type(() => Date) private startTime: Date; @Type(() => Number) @@ -18,9 +17,6 @@ export class CreateRecruitDto { @IsNumber() private pace: number; - @IsNumberString() - private hCode: string; - @Type(() => Number) @IsNumber() private userId: number; @@ -29,15 +25,7 @@ export class CreateRecruitDto { @IsNumber() private courseId: number; - getHCode(): string { - return this.hCode; - } - - setHCodeToName(name: string): void { - this.hCode = name; - } - toEntity(): Recruit { - return Recruit.of(this.title, this.startTime, this.maxPpl, this.pace, this.hCode, this.userId, this.courseId); + return Recruit.of(this.title, this.startTime, this.maxPpl, this.pace, this.userId, this.courseId); } } diff --git a/server/src/recruit/dto/get-recruit.dto.ts b/server/src/recruit/dto/request/get-recruit.request.ts similarity index 100% rename from server/src/recruit/dto/get-recruit.dto.ts rename to server/src/recruit/dto/request/get-recruit.request.ts diff --git a/server/src/recruit/dto/join-recruit.dto.ts b/server/src/recruit/dto/request/join-recruit.request.ts similarity index 86% rename from server/src/recruit/dto/join-recruit.dto.ts rename to server/src/recruit/dto/request/join-recruit.request.ts index aad4dea..b788a57 100644 --- a/server/src/recruit/dto/join-recruit.dto.ts +++ b/server/src/recruit/dto/request/join-recruit.request.ts @@ -1,6 +1,6 @@ import { Type } from "class-transformer"; import { IsNumber } from "class-validator"; -import { UserRecruit } from "../../entities/user_recruit.entity"; +import { UserRecruit } from "src/common/entities/user_recruit.entity"; export class JoinRecruitDto { @Type(() => Number) diff --git a/server/src/recruit/recruit.controller.ts b/server/src/recruit/recruit.controller.ts index cf7fb34..88fa1f5 100644 --- a/server/src/recruit/recruit.controller.ts +++ b/server/src/recruit/recruit.controller.ts @@ -1,29 +1,29 @@ -import { Body, Controller, Get, Post, Query, Param, Req } from "@nestjs/common"; -import { JwtService } from "@nestjs/jwt"; -import { CreateRecruitDto } from "./dto/create-recruit.dto"; -import { GetRecruitDto } from "./dto/get-recruit.dto"; -import { JoinRecruitDto } from "./dto/join-recruit.dto"; +import { Body, Controller, Get, Post, Query, Param, Req, NotFoundException } from "@nestjs/common"; +import { CreateRecruitDto } from "./dto/request/create-recruit.request"; +import { GetRecruitDto } from "./dto/request/get-recruit.request"; +import { JoinRecruitDto } from "./dto/request/join-recruit.request"; import { RecruitService } from "./recruit.service"; import { Request } from "express"; +import { ApiOperation } from "@nestjs/swagger"; @Controller("recruit") export class RecruitController { - constructor(private readonly recruitService: RecruitService, private jwtService: JwtService) {} + constructor(private readonly recruitService: RecruitService) {} + @ApiOperation({ summary: "모집글 조회/검색/필터 API" }) @Get() - async getRecruits(@Query() queryParams: GetRecruitDto) { - const recruitList = await this.recruitService.getRecruitList(queryParams); + async getMany(@Query() queryParams: GetRecruitDto) { + const recruitList = await this.recruitService.getMany(queryParams); return { statusCode: 200, data: recruitList, }; } + @ApiOperation({ summary: "모집글 등록 API" }) @Post() async create(@Body() createRecruitDto: CreateRecruitDto) { const recruitEntity = await this.recruitService.create(createRecruitDto); - // TODO: 응답 리팩토링하기 entity -> 응답 dto 변환 후 인터셉터가 상태코드 넣어서 처리하게끔 바꾸기 - // 매번 상태코드와 데이터 넣어주는 방식이 깔끔하지 못한 느낌. return { statusCode: 201, data: { @@ -31,12 +31,13 @@ export class RecruitController { }, }; } - // @UseGuards(AccessGuard) + + @ApiOperation({ summary: "" }) @Post("join") async register(@Body() joinRecruitDto: JoinRecruitDto) { const recruitId = joinRecruitDto.getRecruitId(); const userId = joinRecruitDto.getUserId(); - if (!(await this.recruitService.isExistRecruit(recruitId))) { + if (!(await this.recruitService.isExistingRecruit(recruitId))) { return { statusCode: 409, error: "conflict", @@ -72,14 +73,12 @@ export class RecruitController { } @Get(":id") - async getRecruitDetail(@Param("id") recruitId: number, @Req() request: Request) { + async getOne(@Param("id") recruitId: number, @Req() request: Request) { const jwtString = request.headers["authorization"].split("Bearer")[1].trim(); - const { userIdx } = this.jwtService.verify(jwtString, { secret: process.env.ACCESS_SECRET }); - const data = await this.recruitService.getRecruitDetail(recruitId); - return { - ...data, - isAuthor: data.authorId === userIdx, - isParticipating: await this.recruitService.isParticipating(recruitId, userIdx), - }; + if (!(await this.recruitService.isExistingRecruit(recruitId))) { + throw new NotFoundException("Does not exist or has been deleted"); + } + const data = await this.recruitService.getOne(jwtString, recruitId); + return data; } } diff --git a/server/src/recruit/recruit.module.ts b/server/src/recruit/recruit.module.ts index 2f4bfc9..42b336c 100644 --- a/server/src/recruit/recruit.module.ts +++ b/server/src/recruit/recruit.module.ts @@ -2,24 +2,17 @@ import { Module } from "@nestjs/common"; import { RecruitService } from "./recruit.service"; import { RecruitController } from "./recruit.controller"; import { TypeOrmCustomModule } from "src/common/typeorm/typeorm.module"; -import { AuthService } from "src/auth/auth.service"; -import { JwtService } from "@nestjs/jwt"; -import { AuthRepository } from "src/auth/auth.repository"; -import { UserRepository } from "src/user/user.repository"; -import { UserRecruitRepository } from "src/user_recruit.repository"; -import { RecruitRepository } from "./recruit.repository"; -import { HDongRepository } from "src/common/repository/h_dong.repository"; +import { UserRepository } from "src/common/repositories/user.repository"; +import { UserRecruitRepository } from "src/common/repositories/user_recruit.repository"; +import { RecruitRepository } from "../common/repositories/recruit.repository"; +import { CustomJwtModule } from "src/common/modules/custom-jwt/custom-jwt.module"; @Module({ imports: [ - TypeOrmCustomModule.forCustomRepository([ - RecruitRepository, - UserRepository, - UserRecruitRepository, - HDongRepository, - ]), + TypeOrmCustomModule.forCustomRepository([RecruitRepository, UserRepository, UserRecruitRepository]), + CustomJwtModule, ], + providers: [RecruitService], controllers: [RecruitController], - providers: [RecruitService, AuthService, JwtService, AuthRepository], }) export class RecruitModule {} diff --git a/server/src/recruit/recruit.service.ts b/server/src/recruit/recruit.service.ts index 4ebb7ac..cd79d7f 100644 --- a/server/src/recruit/recruit.service.ts +++ b/server/src/recruit/recruit.service.ts @@ -1,28 +1,38 @@ import { Injectable } from "@nestjs/common"; -import { RecruitRepository } from "./recruit.repository"; -import { CreateRecruitDto } from "./dto/create-recruit.dto"; -import { GetRecruitDto } from "./dto/get-recruit.dto"; -import { Recruit } from "src/entities/recruit.entity"; -import { UserRecruitRepository } from "src/user_recruit.repository"; -import { HDongRepository } from "src/common/repository/h_dong.repository"; +import { RecruitRepository } from "../common/repositories/recruit.repository"; +import { CreateRecruitDto } from "./dto/request/create-recruit.request"; +import { GetRecruitDto } from "./dto/request/get-recruit.request"; +import { Recruit } from "src/common/entities/recruit.entity"; +import { UserRecruitRepository } from "src/common/repositories/user_recruit.repository"; import { plainToGetRecruitDto } from "src/common/utils/plainToGetRecruitDto"; +import { CustomJwtService } from "src/common/modules/custom-jwt/custom-jwt.service"; +import { DataSource, FindOneOptions, Repository } from "typeorm"; @Injectable() export class RecruitService { constructor( private recruitRepository: RecruitRepository, - private hDongRepository: HDongRepository, private userRecruitRepository: UserRecruitRepository, + private jwtService: CustomJwtService, + private dataSource: DataSource, ) {} - async create(createRecruitDto: CreateRecruitDto): Promise { - const code = createRecruitDto.getHCode(); - const { name } = await this.hDongRepository.findOneBy({ code }); - createRecruitDto.setHCodeToName(name); - const recruitEntity = createRecruitDto.toEntity(); - return this.recruitRepository.createOne(recruitEntity); + async create(createRecruitDto: CreateRecruitDto) { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const recruitEntity = await this.recruitRepository.createOne(createRecruitDto.toEntity()); + this.userRecruitRepository.createUserRecruit(recruitEntity.userId, recruitEntity.id); + return recruitEntity; + } catch (error: any) { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } } - async getRecruitList(queryParams: GetRecruitDto) { + async getMany(queryParams: GetRecruitDto) { if (queryParams.getQuery() === "") { return []; } @@ -55,16 +65,32 @@ export class RecruitService { .map(plainToGetRecruitDto); } - async getRecruitDetail(recruitId: number) { - return await this.recruitRepository.findRecruitDetail(recruitId); + async getOne(jwtString: string, recruitId: number) { + const { userIdx } = this.jwtService.verifyAccessToken(jwtString); + const data = await this.recruitRepository.findRecruitDetail(recruitId); + const { title, maxPpl, pace, userId, currentPpl, path, pathLength, startTime } = data; + + return { + title, + maxPpl, + pace, + userId, + currentPpl, + path, + pathLength, + startTime, + hDong: { name: data.h_dong_name }, + isAuthor: data.authorId === userIdx, + isParticipating: await this.isParticipating(recruitId, userIdx), + }; } - async isExistRecruit(recruitId: number): Promise { + async isExistingRecruit(recruitId: number): Promise { const recruitEntity = await this.recruitRepository.findOneById(recruitId); if (recruitEntity) { - return recruitEntity.userId; + return true; } - return null; + return false; } async isParticipating(recruitId: number, userId: number): Promise { diff --git a/server/src/user/dto/check-user.dto.ts b/server/src/user/dto/check-user.dto.ts index 8c6ecbc..88f5dbc 100644 --- a/server/src/user/dto/check-user.dto.ts +++ b/server/src/user/dto/check-user.dto.ts @@ -1,4 +1,4 @@ -import { IsValidId } from "src/common/decorator"; +import { IsValidId } from "src/common/decorators"; export class CheckUserDto { @IsValidId() diff --git a/server/src/user/dto/create-user.dto.ts b/server/src/user/dto/create-user.dto.ts index 6f0ca0e..0a1f7cf 100644 --- a/server/src/user/dto/create-user.dto.ts +++ b/server/src/user/dto/create-user.dto.ts @@ -1,7 +1,7 @@ import { Type } from "class-transformer"; import { IsNumber, IsNumberString } from "class-validator"; -import { IsValidId, IsValidPassword } from "src/common/decorator"; -import { User } from "../../entities/user.entity"; +import { IsValidId, IsValidPassword } from "src/common/decorators"; +import { User } from "../../common/entities/user.entity"; export class CreateUserDto { @IsValidId() diff --git a/server/src/user/user.module.ts b/server/src/user/user.module.ts index 7051fa6..4ccfb1c 100644 --- a/server/src/user/user.module.ts +++ b/server/src/user/user.module.ts @@ -1,11 +1,10 @@ import { Module } from "@nestjs/common"; +import { UserRepository } from "../common/repositories/user.repository"; import { UserService } from "./user.service"; import { UserController } from "./user.controller"; import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmCustomModule } from "src/common/typeorm/typeorm.module"; -import { User } from "src/entities/user.entity"; -import { UserRepository } from "./user.repository"; -import { UserRecruitRepository } from "src/user_recruit.repository"; +import { User } from "src/common/entities/user.entity"; @Module({ imports: [TypeOrmModule.forFeature([User]), TypeOrmCustomModule.forCustomRepository([UserRepository])], diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index 8c04671..e9fea77 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import * as bcrypt from "bcryptjs"; -import { UserRepository } from "./user.repository"; +import { UserRepository } from "../common/repositories/user.repository"; import { CreateUserDto } from "./dto/create-user.dto"; import { CheckUserDto } from "./dto/check-user.dto"; @@ -31,8 +31,4 @@ export class UserService { existsCode: false, }; } - - async getUserIdxByUserId(userId: string) { - return await this.userRepository.findUserIdxByUserId(userId); - } } From 693fa01b4408b7000dd9a397e7da30977a609b50 Mon Sep 17 00:00:00 2001 From: catensia Date: Wed, 30 Nov 2022 19:15:58 +0900 Subject: [PATCH 4/7] WIP: attach APIs to main page --- client/src/App.tsx | 2 +- client/src/hooks/queries/useCoursesQuery.ts | 10 ++++++++++ client/src/pages/{Main2 => Main}/MainPage.data.ts | 0 client/src/pages/{Main2 => Main}/MainPage.styles.ts | 0 client/src/pages/{Main2 => Main}/MainPage.tsx | 11 +++++++++-- client/src/types/dto/GetCoursesRes.ts | 8 ++++++++ 6 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 client/src/hooks/queries/useCoursesQuery.ts rename client/src/pages/{Main2 => Main}/MainPage.data.ts (100%) rename client/src/pages/{Main2 => Main}/MainPage.styles.ts (100%) rename client/src/pages/{Main2 => Main}/MainPage.tsx (83%) create mode 100644 client/src/types/dto/GetCoursesRes.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index ca887cb..3559bdb 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -2,7 +2,7 @@ import React from "react"; import SignUp from "#pages/SignUp/SignUp"; import Login from "#pages/Login/Login"; import MenuPage from "#pages/Menu/MenuPage"; -import MainPage from "#pages/Main2/MainPage"; +import MainPage from "#pages/Main/MainPage"; import Courses from "#pages/Courses/Courses"; import { Route, Routes } from "react-router-dom"; import NewCourse from "#pages/NewCourse/NewCourse"; diff --git a/client/src/hooks/queries/useCoursesQuery.ts b/client/src/hooks/queries/useCoursesQuery.ts new file mode 100644 index 0000000..e7bab75 --- /dev/null +++ b/client/src/hooks/queries/useCoursesQuery.ts @@ -0,0 +1,10 @@ +import useHttpGet from "#hooks/http/useHttpGet"; +import GetCoursesRes from "#types/dto/GetCoursesRes"; +import { useQuery } from "@tanstack/react-query"; + +const useCoursesQuery = () => { + const { get } = useHttpGet(); + return useQuery(["course"], async () => get(`/course`).then((res) => res || {})); +}; + +export default useCoursesQuery; diff --git a/client/src/pages/Main2/MainPage.data.ts b/client/src/pages/Main/MainPage.data.ts similarity index 100% rename from client/src/pages/Main2/MainPage.data.ts rename to client/src/pages/Main/MainPage.data.ts diff --git a/client/src/pages/Main2/MainPage.styles.ts b/client/src/pages/Main/MainPage.styles.ts similarity index 100% rename from client/src/pages/Main2/MainPage.styles.ts rename to client/src/pages/Main/MainPage.styles.ts diff --git a/client/src/pages/Main2/MainPage.tsx b/client/src/pages/Main/MainPage.tsx similarity index 83% rename from client/src/pages/Main2/MainPage.tsx rename to client/src/pages/Main/MainPage.tsx index 4eb8cf9..6ecf53e 100644 --- a/client/src/pages/Main2/MainPage.tsx +++ b/client/src/pages/Main/MainPage.tsx @@ -4,7 +4,9 @@ import Slider, { Settings } from "react-slick"; import CourseCard from "#components/Card/CourseCard/CourseCard"; import { CarouselWrapper, ListTitle, MainPageContainer, TitleWrapper } from "./MainPage.styles"; import RecruitTextCard from "#components/Card/RecruitTextCard/RecruitTextCard"; -import { course, recruit } from "./MainPage.data"; +import useCoursesQuery from "#hooks/queries/useCoursesQuery"; +import { recruit } from "./MainPage.data"; +import { useEffect } from "react"; const settings: Settings = { centerMode: true, @@ -15,6 +17,11 @@ const settings: Settings = { }; const MainPage = () => { + const { data: course, isLoading } = useCoursesQuery(); + + if (isLoading) return
    Loading...
    ; + if (!course) return
    404
    ; + return ( <>
    @@ -26,7 +33,7 @@ const MainPage = () => { - {new Array(10).fill(course).map((c, idx) => ( + {course.data.map((c: any, idx: any) => ( ))} diff --git a/client/src/types/dto/GetCoursesRes.ts b/client/src/types/dto/GetCoursesRes.ts new file mode 100644 index 0000000..530e8b9 --- /dev/null +++ b/client/src/types/dto/GetCoursesRes.ts @@ -0,0 +1,8 @@ +import { Course } from "#types/Course"; + +interface GetCoursesRes { + statusCode: number; + data: Course[]; +} + +export default GetCoursesRes; From ab23e4974ec27ef90564747a06660506d410dd22 Mon Sep 17 00:00:00 2001 From: catensia Date: Thu, 1 Dec 2022 02:00:02 +0900 Subject: [PATCH 5/7] feat: Implement main page --- client/src/hooks/queries/useCoursesQuery.ts | 7 +-- .../hooks/queries/useRecruitDetailQuery.ts | 8 ++-- client/src/hooks/queries/useRecruitsQuery.ts | 11 +++++ client/src/pages/Main/MainPage.data.ts | 43 ------------------- client/src/pages/Main/MainPage.tsx | 16 +++---- .../src/pages/RecruitDetail/RecruitDetail.tsx | 15 +++---- client/src/types/RecruitDetail.ts | 16 +++++++ .../http-request-body.interceptor.ts | 2 +- server/src/course/course.controller.ts | 2 +- server/src/course/course.service.ts | 2 +- 10 files changed, 52 insertions(+), 70 deletions(-) create mode 100644 client/src/hooks/queries/useRecruitsQuery.ts delete mode 100644 client/src/pages/Main/MainPage.data.ts create mode 100644 client/src/types/RecruitDetail.ts diff --git a/client/src/hooks/queries/useCoursesQuery.ts b/client/src/hooks/queries/useCoursesQuery.ts index e7bab75..ebe497a 100644 --- a/client/src/hooks/queries/useCoursesQuery.ts +++ b/client/src/hooks/queries/useCoursesQuery.ts @@ -1,10 +1,11 @@ import useHttpGet from "#hooks/http/useHttpGet"; -import GetCoursesRes from "#types/dto/GetCoursesRes"; +import { Course } from "#types/Course"; +import HttpResponse from "#types/dto/HttpResponse"; import { useQuery } from "@tanstack/react-query"; const useCoursesQuery = () => { - const { get } = useHttpGet(); - return useQuery(["course"], async () => get(`/course`).then((res) => res || {})); + const { get } = useHttpGet>(); + return useQuery>(["course"], async () => get(`/course`).then((res) => res || {})); }; export default useCoursesQuery; diff --git a/client/src/hooks/queries/useRecruitDetailQuery.ts b/client/src/hooks/queries/useRecruitDetailQuery.ts index cad4e72..8d15783 100644 --- a/client/src/hooks/queries/useRecruitDetailQuery.ts +++ b/client/src/hooks/queries/useRecruitDetailQuery.ts @@ -1,12 +1,10 @@ import useHttpGet from "#hooks/http/useHttpGet"; import HttpResponse from "#types/dto/HttpResponse"; -import { Recruit } from "#types/Recruit"; +import { RecruitDetail } from "#types/RecruitDetail"; import { useQuery } from "@tanstack/react-query"; const useRecruitDetailQuery = (id: number) => { - const { get } = useHttpGet>(); - return useQuery(["recruit", id], async () => get(`/recruit/${id}`).then((res) => res.data), { - refetchInterval: 2000, - }); + const { get } = useHttpGet>(); + return useQuery(["recruit", id], async () => get(`/recruit/${id}`).then((res) => res.data)); }; export default useRecruitDetailQuery; diff --git a/client/src/hooks/queries/useRecruitsQuery.ts b/client/src/hooks/queries/useRecruitsQuery.ts new file mode 100644 index 0000000..11932e1 --- /dev/null +++ b/client/src/hooks/queries/useRecruitsQuery.ts @@ -0,0 +1,11 @@ +import useHttpGet from "#hooks/http/useHttpGet"; +import HttpResponse from "#types/dto/HttpResponse"; +import { Recruit } from "#types/Recruit"; +import { useQuery } from "@tanstack/react-query"; + +const useRecruitsQuery = () => { + const { get } = useHttpGet>(); + return useQuery>(["recruit"], async () => get(`/recruit`).then((res) => res || {})); +}; + +export default useRecruitsQuery; diff --git a/client/src/pages/Main/MainPage.data.ts b/client/src/pages/Main/MainPage.data.ts deleted file mode 100644 index ed78e47..0000000 --- a/client/src/pages/Main/MainPage.data.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Course } from "#types/Course"; -import { Recruit } from "#types/Recruit"; - -export const course: Course = { - id: 1, - title: "Dirty Ho (Lan tou He)", - img: "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png", - path: JSON.stringify([ - { lat: 126.57091836134346, lng: 33.45090000378721 }, - { lat: 126.57004847387998, lng: 33.450635526049844 }, - { lat: 126.56931524544794, lng: 33.45101165404891 }, - { lat: 126.56932224193068, lng: 33.44959616387136 }, - { lat: 126.5700747443057, lng: 33.449670903389 }, - { lat: 126.570502727405, lng: 33.450123187413496 }, - ]), - pathLength: 60, - hDong: { - name: "", - }, - createdAt: "2022-11-21T08:55:33.162Z", -}; - -export const recruit: Recruit = { - id: 125, - title: "gustas", - startTime: "2022-11-25T14:50:00.000Z", - maxPpl: 4, - currentPpl: 1, - userId: "guss95", - createdAt: "2022-11-24T15:04:46.095Z", - pace: 330, - course: { - id: 1, - title: "Dirty Ho (Lan tou He)", - img: "https://kr.object.ncloudstorage.com/j199/img/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202022-11-20%20%EC%98%A4%ED%9B%84%204.01.56.png", - path: "", - pathLength: 60, - hDong: { - name: "잠실동", - }, - createdAt: "2022-11-21T08:55:33.162Z", - }, -}; diff --git a/client/src/pages/Main/MainPage.tsx b/client/src/pages/Main/MainPage.tsx index 6ecf53e..c786d09 100644 --- a/client/src/pages/Main/MainPage.tsx +++ b/client/src/pages/Main/MainPage.tsx @@ -5,8 +5,8 @@ import CourseCard from "#components/Card/CourseCard/CourseCard"; import { CarouselWrapper, ListTitle, MainPageContainer, TitleWrapper } from "./MainPage.styles"; import RecruitTextCard from "#components/Card/RecruitTextCard/RecruitTextCard"; import useCoursesQuery from "#hooks/queries/useCoursesQuery"; -import { recruit } from "./MainPage.data"; import { useEffect } from "react"; +import useRecruitsQuery from "#hooks/queries/useRecruitsQuery"; const settings: Settings = { centerMode: true, @@ -17,10 +17,10 @@ const settings: Settings = { }; const MainPage = () => { - const { data: course, isLoading } = useCoursesQuery(); - - if (isLoading) return
    Loading...
    ; - if (!course) return
    404
    ; + const { data: course, isLoading: coursesLoading } = useCoursesQuery(); + const { data: recruit, isLoading: recruitsLoading } = useRecruitsQuery(); + if (coursesLoading || recruitsLoading) return
    Loading...
    ; + if (!course || !recruit) return
    404
    ; return ( <> @@ -29,7 +29,7 @@ const MainPage = () => {
    코스 목록 - + @@ -42,11 +42,11 @@ const MainPage = () => {
    모집 목록 - + - {new Array(10).fill(recruit).map((r, idx) => ( + {recruit.data.map((r: any, idx: any) => ( ))} diff --git a/client/src/pages/RecruitDetail/RecruitDetail.tsx b/client/src/pages/RecruitDetail/RecruitDetail.tsx index ebff2ea..da54926 100644 --- a/client/src/pages/RecruitDetail/RecruitDetail.tsx +++ b/client/src/pages/RecruitDetail/RecruitDetail.tsx @@ -15,12 +15,14 @@ const RecruitDetail = () => { const { data, isLoading } = useRecruitDetailQuery(Number(id)); const { post } = useHttpPost(); - post("/recruit/join", { recruitId: String(id) }); + + if (isLoading) return
    Loading...
    ; + if (!data) return
    404
    ; const renderMap = useCallback( useShowMap({ height: `${window.innerHeight - 307}px`, - center: getMiddlePoint(typeof data?.course.path === "string" ? JSON.parse(data?.course.path || `[]`) : []), - runningPath: typeof data?.course.path === "string" ? JSON.parse(data?.course.path || `[]`) : [], + center: getMiddlePoint(data.path), + runningPath: data.path, level: 5, }).renderMap, [data], @@ -32,9 +34,6 @@ const RecruitDetail = () => { } catch {} }, []); - if (isLoading) return
    Loading...
    ; - if (!data) return
    404
    ; - return ( <>
    @@ -43,11 +42,11 @@ const RecruitDetail = () => {
    출발점 -

    {data.course.hDong.name}

    +

    {data.hDong.name}

    총거리 -

    {data.course.pathLength}km

    +

    {data.pathLength}km

    페이스 diff --git a/client/src/types/RecruitDetail.ts b/client/src/types/RecruitDetail.ts new file mode 100644 index 0000000..932412c --- /dev/null +++ b/client/src/types/RecruitDetail.ts @@ -0,0 +1,16 @@ +import { hDong } from "./hDong"; +import { LatLng } from "./LatLng"; + +export interface RecruitDetail { + title: string; + startTime: string; + maxPpl: number; + currentPpl: number; + path: LatLng[]; + pathLength: number; + pace: number; + hDong: hDong; + userId: string; + isParticipating: boolean; + isAuthor: boolean; +} diff --git a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts index 7cb69cc..dece7dd 100644 --- a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts +++ b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts @@ -8,7 +8,7 @@ export class HttpRequestBodyInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); - const accessToken = request.headers["authorization"]; + const accessToken = request.headers["authorization"].split("Bearer")[1].trim(); if (accessToken) { try { diff --git a/server/src/course/course.controller.ts b/server/src/course/course.controller.ts index a563a2f..350ed16 100644 --- a/server/src/course/course.controller.ts +++ b/server/src/course/course.controller.ts @@ -38,6 +38,6 @@ export class CourseController { throw new NotFoundException("Does not exist or has been deleted"); } const data = await this.courseService.getOne(courseId); - return data; + return { statusCode: 200, data }; } } diff --git a/server/src/course/course.service.ts b/server/src/course/course.service.ts index dc06d10..a1c8ff5 100644 --- a/server/src/course/course.service.ts +++ b/server/src/course/course.service.ts @@ -50,7 +50,7 @@ export class CourseService { async getOne(recruitId: number) { const data = await this.courseRepository.findCourseDetail(recruitId); const { title, path, pathLength } = data; - return { title, path, pathLength, hDong: data.hCode, userId: data.user.userId }; + return { title, path: JSON.parse(path), pathLength, hDong: data.hCode, userId: data.user.userId }; } async isExistingCourse(recruitId: number): Promise { From b10c4a8a6ebfecc92a10d67553bd3c9a63671592 Mon Sep 17 00:00:00 2001 From: catensia Date: Thu, 1 Dec 2022 12:56:31 +0900 Subject: [PATCH 6/7] chore: resolve build errors --- client/src/pages/Main/MainPage.tsx | 1 - .../interceptors/http-request/http-request-body.interceptor.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/pages/Main/MainPage.tsx b/client/src/pages/Main/MainPage.tsx index c786d09..fa74cb2 100644 --- a/client/src/pages/Main/MainPage.tsx +++ b/client/src/pages/Main/MainPage.tsx @@ -5,7 +5,6 @@ import CourseCard from "#components/Card/CourseCard/CourseCard"; import { CarouselWrapper, ListTitle, MainPageContainer, TitleWrapper } from "./MainPage.styles"; import RecruitTextCard from "#components/Card/RecruitTextCard/RecruitTextCard"; import useCoursesQuery from "#hooks/queries/useCoursesQuery"; -import { useEffect } from "react"; import useRecruitsQuery from "#hooks/queries/useRecruitsQuery"; const settings: Settings = { diff --git a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts index bd7bc9d..beefc0f 100644 --- a/server/src/common/interceptors/http-request/http-request-body.interceptor.ts +++ b/server/src/common/interceptors/http-request/http-request-body.interceptor.ts @@ -8,7 +8,7 @@ export class HttpRequestBodyInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); - const accessToken = request.headers["authorization"].split("Bearer")[1].trim(); + let accessToken = request.headers["authorization"]; if (accessToken) { try { From b3420e0e10efc35240cd48c1a8ff9012c71a4d42 Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Thu, 1 Dec 2022 13:24:00 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20#155=20=EB=88=84=EB=9D=BD=EB=90=9C?= =?UTF-8?q?=20=ED=83=80=EC=9E=85=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Main/MainPage.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Main/MainPage.tsx b/client/src/pages/Main/MainPage.tsx index fa74cb2..4456b71 100644 --- a/client/src/pages/Main/MainPage.tsx +++ b/client/src/pages/Main/MainPage.tsx @@ -6,6 +6,8 @@ import { CarouselWrapper, ListTitle, MainPageContainer, TitleWrapper } from "./M import RecruitTextCard from "#components/Card/RecruitTextCard/RecruitTextCard"; import useCoursesQuery from "#hooks/queries/useCoursesQuery"; import useRecruitsQuery from "#hooks/queries/useRecruitsQuery"; +import { Course } from "#types/Course"; +import { Recruit } from "#types/Recruit"; const settings: Settings = { centerMode: true, @@ -32,7 +34,7 @@ const MainPage = () => { - {course.data.map((c: any, idx: any) => ( + {course.data.map((c: Course, idx: number) => ( ))} @@ -45,7 +47,7 @@ const MainPage = () => { - {recruit.data.map((r: any, idx: any) => ( + {recruit.data.map((r: Recruit, idx: number) => ( ))}