From 7fb8cf260b7f8dec88ff20ac388d81408a81b490 Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Thu, 24 Nov 2022 22:38:25 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20#79=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useLocalAPI.ts | 17 +++++ .../src/pages/NewCourse/NewCourse.styles.ts | 20 ++++++ client/src/pages/NewCourse/NewCourse.tsx | 69 ++++++++++++------- client/src/types/Region.ts | 16 +++++ client/src/utils/mapUtils.ts | 7 ++ client/src/utils/valitationUtils.ts | 7 +- 6 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 client/src/hooks/useLocalAPI.ts create mode 100644 client/src/pages/NewCourse/NewCourse.styles.ts create mode 100644 client/src/types/Region.ts create mode 100644 client/src/utils/mapUtils.ts diff --git a/client/src/hooks/useLocalAPI.ts b/client/src/hooks/useLocalAPI.ts new file mode 100644 index 0000000..200cd5c --- /dev/null +++ b/client/src/hooks/useLocalAPI.ts @@ -0,0 +1,17 @@ +import Axios, { AxiosResponse } from "axios"; +import { useCallback } from "react"; +type RequestParams = { [key: string]: any }; +const axios = Axios.create({ + baseURL: "https://dapi.kakao.com/v2/local", + headers: { + Authorization: `KakaoAK ${process.env.REACT_APP_KAKAO_REST_KEY}`, + }, +}); +const useLocalAPI = (url: string) => { + const query = useCallback((params: RequestParams): Promise => { + return axios.get>(url, { params }).then((res) => res.data); + }, []); + + return query; +}; +export default useLocalAPI; diff --git a/client/src/pages/NewCourse/NewCourse.styles.ts b/client/src/pages/NewCourse/NewCourse.styles.ts new file mode 100644 index 0000000..cedef3c --- /dev/null +++ b/client/src/pages/NewCourse/NewCourse.styles.ts @@ -0,0 +1,20 @@ +import styled from "styled-components"; +import { COLOR } from "styles/color"; +import { flexColumn } from "styles/flex"; + +export const CourseForm = styled.div` + ${flexColumn}; + align-items: center; + width: 100%; + height: "240px"; + box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.25); + padding: 15px 27px; + div { + margin-bottom: 10px; + width: 100%; + p { + padding: 16px; + border-bottom: 1px solid ${COLOR.BABY_BLUE}; + } + } +`; diff --git a/client/src/pages/NewCourse/NewCourse.tsx b/client/src/pages/NewCourse/NewCourse.tsx index 56b614a..e7844fb 100644 --- a/client/src/pages/NewCourse/NewCourse.tsx +++ b/client/src/pages/NewCourse/NewCourse.tsx @@ -1,34 +1,55 @@ +//#region import import Button from "#components/Button/Button"; import Header from "#components/Header/Header"; import Input from "#components/Input/Input"; -import { PLACEHOLDER } from "#constants/placeholder"; -import styled from "styled-components"; -import { COLOR } from "styles/color"; -import { flexColumn } from "styles/flex"; import useWriteMap from "#hooks/useWriteMap"; -const CourseForm = styled.div` - ${flexColumn}; - align-items: center; - width: 100%; - height: "240px"; - box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.25); - padding: 15px 27px; - div { - margin-bottom: 10px; - width: 100%; - p { - padding: 16px; - border-bottom: 1px solid ${COLOR.BABY_BLUE}; - } - } -`; - +import useHttpPost from "#hooks/http/useHttpPost"; +import useInput from "#hooks/useInput"; +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 { RegionResponse } from "#types/Region"; +import { CourseForm } from "./NewCourse.styles"; +//#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 { renderMap, pathLength } = useWriteMap({ + const { userIdx: userId } = useRecoilValue(userState); + const [title, onChangeTitle] = useInput(courseTitleValidator); + const query = useLocalAPI("/geo/coord2regioncode.json"); + const { post } = useHttpPost(); + const navigate = useNavigate(); + + const { renderMap, pathLength, path } = useWriteMap({ height: `${window.innerHeight - 307}px`, center: { lat: 33.450701, lng: 126.570667 }, }); + const onClickInsertButton = useCallback(async () => { + if (!title || !path.length) return; + try { + const { lng: x, lat: y } = getLatLngByXY(path[0]); + const regions = await query({ x, y }); + const hCode = regions.documents.find((el) => el.region_type === "H")?.code; + const response = await post("/course", { + title, + path: path.map(getLatLngByXY), + img, + pathLength, + userId, + hCode, + }); + navigate(`/course/${response.courseId}`); + } catch (error: any) { + alert(error.message); + } + }, [path]); + return (
@@ -40,9 +61,9 @@ const NewCourse = () => {
코스명 - +
- diff --git a/client/src/types/Region.ts b/client/src/types/Region.ts new file mode 100644 index 0000000..266c77a --- /dev/null +++ b/client/src/types/Region.ts @@ -0,0 +1,16 @@ +export interface RegionResponse { + documents: Region[]; + meta: { total_count: number }; +} + +interface Region { + address_name: string; + code: string; + region_1depth_name: string; + region_2depth_name: string; + region_3depth_name: string; + region_4depth_name: string; + region_type: string; + x: string; + y: string; +} diff --git a/client/src/utils/mapUtils.ts b/client/src/utils/mapUtils.ts new file mode 100644 index 0000000..3722021 --- /dev/null +++ b/client/src/utils/mapUtils.ts @@ -0,0 +1,7 @@ +import { LatLng } from "#types/LatLng"; + +const getLatLngByPoint = (point: kakao.maps.LatLng): LatLng => { + return { lat: point.getLat(), lng: point.getLng() }; +}; + +export default getLatLngByPoint; diff --git a/client/src/utils/valitationUtils.ts b/client/src/utils/valitationUtils.ts index 2c15314..2aa2ee3 100644 --- a/client/src/utils/valitationUtils.ts +++ b/client/src/utils/valitationUtils.ts @@ -8,10 +8,15 @@ export const idValidator = (id: string) => { export const passwordValidator = (password: string) => { return password.length < 10 || password.length > 100 ? "비밀번호는 10자 이상 100자 이하여야 합니다" : ""; }; + export const confirmPasswordValidator = (password: string) => (confirmPassword: string) => { return password !== confirmPassword || !confirmPassword ? "비밀번호가 일치하지 않습니다" : ""; }; export const hCodeValidator = (hCode: string) => { - return hCode.length === 5 ? "" : "지역을 입력하세요"; + return hCode.length === 10 ? "" : "지역을 입력하세요"; +}; + +export const courseTitleValidator = (title: string) => { + return !title ? "제목을 입력하세요" : ""; }; From 9fafb6400300fa5c58d3d42dab442847b55f8daa Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Thu, 24 Nov 2022 23:19:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20#79=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20hName=20=EC=9E=85=EB=A0=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/NewCourse/NewCourse.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/src/pages/NewCourse/NewCourse.tsx b/client/src/pages/NewCourse/NewCourse.tsx index e7844fb..52a8ab1 100644 --- a/client/src/pages/NewCourse/NewCourse.tsx +++ b/client/src/pages/NewCourse/NewCourse.tsx @@ -35,7 +35,8 @@ const NewCourse = () => { try { const { lng: x, lat: y } = getLatLngByXY(path[0]); const regions = await query({ x, y }); - const hCode = regions.documents.find((el) => el.region_type === "H")?.code; + // [0]: BCode, [1]: HCode + const { code: hCode, region_3depth_name: name } = regions.documents[1]; const response = await post("/course", { title, path: path.map(getLatLngByXY), @@ -43,6 +44,7 @@ const NewCourse = () => { pathLength, userId, hCode, + name, }); navigate(`/course/${response.courseId}`); } catch (error: any) { From cddcf19d05a424067ba1dffc66d5f533a9a2a0cd Mon Sep 17 00:00:00 2001 From: choigeon96 Date: Thu, 24 Nov 2022 23:21:09 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20#79=20=EC=BD=94=EC=8A=A4=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20hName=20=EC=9E=85=EB=A0=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/course/dto/create-course.dto.ts | 7 ++++--- server/src/entities/course.entity.ts | 13 +++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/server/src/course/dto/create-course.dto.ts b/server/src/course/dto/create-course.dto.ts index 99848f6..60e5e87 100644 --- a/server/src/course/dto/create-course.dto.ts +++ b/server/src/course/dto/create-course.dto.ts @@ -1,4 +1,3 @@ -import { Type } from "class-transformer"; import { IsNumber, IsNumberString, IsString } from "class-validator"; import { isValidPath } from "src/common/decorator/path.validator"; import { LatLng } from "src/common/type/lat-lng"; @@ -17,14 +16,16 @@ export class CreateCourseDto { @IsNumber() private pathLength: number; - @Type(() => Number) @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); + return Course.of(this.title, this.img, this.path, this.pathLength, this.hCode, this.userId, this.name); } } diff --git a/server/src/entities/course.entity.ts b/server/src/entities/course.entity.ts index 89f7191..f5c7445 100644 --- a/server/src/entities/course.entity.ts +++ b/server/src/entities/course.entity.ts @@ -20,7 +20,7 @@ export class Course { @Column() pathLength: number; - @Column({ type: "varchar", length: 10 }) + @Column({ type: "varchar", length: 10, nullable: true }) name: string; @CreateDateColumn() @@ -39,7 +39,15 @@ export class Course { @JoinColumn({ name: "userId", referencedColumnName: "id" }) user: User; - static of(title: string, img: string, path: LatLng[], pathLength: number, hCode: string, userId: number) { + static of( + title: string, + img: string, + path: LatLng[], + pathLength: number, + hCode: string, + userId: number, + name: string, + ) { const course = new Course(); course.title = title; course.img = img; @@ -47,6 +55,7 @@ export class Course { course.hCode = hCode; course.pathLength = pathLength; course.userId = userId; + course.name = name; return course; } }