From 7f55b1dc0fa609de3c40418bb4219c7f83c21435 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Thu, 18 Jan 2024 16:21:49 +0900 Subject: [PATCH 01/50] Feat: delete phoneNumber underline --- .../Contents/Information/BasicInformation/BasicInformation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Detail/Contents/Information/BasicInformation/BasicInformation.tsx b/src/components/Detail/Contents/Information/BasicInformation/BasicInformation.tsx index 0a284665..e20fe598 100644 --- a/src/components/Detail/Contents/Information/BasicInformation/BasicInformation.tsx +++ b/src/components/Detail/Contents/Information/BasicInformation/BasicInformation.tsx @@ -19,7 +19,7 @@ function BasicInformation() {
- 031-771-1234 + 031-771-1234
From 2b41df34ba9252bcc5129d786bbf419e7c8409a9 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Thu, 18 Jan 2024 16:24:14 +0900 Subject: [PATCH 02/50] Feat: delete recommend others reviews count --- .../Information/Others/OtherCard/OtherCard.module.scss | 5 ----- .../Contents/Information/Others/OtherCard/OtherCard.tsx | 3 --- 2 files changed, 8 deletions(-) diff --git a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.module.scss b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.module.scss index f03d6568..7dd44631 100644 --- a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.module.scss +++ b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.module.scss @@ -40,11 +40,6 @@ @include typography(captionSmall); margin-right: 4px; } - - &__reviewsCount { - color: $neutral400; - @include typography(captionSmall); - } } } } diff --git a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx index 1ec0e776..9cd28f41 100644 --- a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx +++ b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx @@ -24,9 +24,6 @@ function OtherCard({ {point} - - ({count}) -
From 774afd969c1f1025a13eb4dfd75829b89b26f074 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Thu, 18 Jan 2024 16:25:49 +0900 Subject: [PATCH 03/50] Feat: delete recommend others reviews count props --- .../Contents/Information/Others/OtherCard/OtherCard.tsx | 8 +------- .../Detail/Contents/Information/Others/Others.tsx | 1 - src/types/detail.ts | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx index 9cd28f41..f6d22cac 100644 --- a/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx +++ b/src/components/Detail/Contents/Information/Others/OtherCard/OtherCard.tsx @@ -4,13 +4,7 @@ import styles from "./OtherCard.module.scss"; import { OtherCardPropsType } from "@/types/detail"; -function OtherCard({ - image, - name, - location, - point, - count, -}: OtherCardPropsType) { +function OtherCard({ image, name, location, point }: OtherCardPropsType) { return (
diff --git a/src/components/Detail/Contents/Information/Others/Others.tsx b/src/components/Detail/Contents/Information/Others/Others.tsx index 4c1ceba6..a43e3872 100644 --- a/src/components/Detail/Contents/Information/Others/Others.tsx +++ b/src/components/Detail/Contents/Information/Others/Others.tsx @@ -91,7 +91,6 @@ function Others() { name={data.name} location={data.location} point={data.point} - count={data.count} /> ))}
diff --git a/src/types/detail.ts b/src/types/detail.ts index cf541f81..5ea37825 100644 --- a/src/types/detail.ts +++ b/src/types/detail.ts @@ -5,7 +5,6 @@ export interface OtherCardPropsType { name: string; location: string; point: string; - count: number; } export interface ReviewPropsTypes { From 90490303f4e8af2a95862455858083f86cbff254 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Fri, 19 Jan 2024 02:26:24 +0900 Subject: [PATCH 04/50] Feat: create title heart, share btn toast --- src/components/Detail/Main/Title/Title.tsx | 47 ++++++++++++++++++- .../MeatballBottomSlide.tsx | 17 ++++++- src/recoil/detail/detail.ts | 5 ++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/components/Detail/Main/Title/Title.tsx b/src/components/Detail/Main/Title/Title.tsx index 87d6f470..f2dc8742 100644 --- a/src/components/Detail/Main/Title/Title.tsx +++ b/src/components/Detail/Main/Title/Title.tsx @@ -1,10 +1,33 @@ +import { useEffect } from "react"; import { FaRegHeart } from "react-icons/fa"; +import { FaHeart } from "react-icons/fa"; import { GoStarFill } from "react-icons/go"; import { IoShareSocialOutline } from "react-icons/io5"; +import { useRecoilState } from "recoil"; import styles from "./Title.module.scss"; +import CustomToast from "@/components/CustomToast/CustomToast"; + +import { IsHeartValued } from "@/recoil/detail/detail"; + function Title() { + const [isHeart, setIsHeart] = useRecoilState(IsHeartValued); + + const showToast = CustomToast(); + + const handleHeartClick = () => { + if (!isHeart) { + showToast("찜 목록에 저장되었습니다."); + } + + setIsHeart(!isHeart); + }; + + useEffect(() => { + setIsHeart(true); + }, []); + return (

롯데시티 호텔

@@ -17,8 +40,28 @@ function Title() {
- - + {isHeart ? ( + + ) : ( + + )} + + { + showToast("링크가 복사되었습니다."); + }} + />
); diff --git a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx index aaa6d6f7..7f830fdd 100644 --- a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx +++ b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx @@ -2,12 +2,14 @@ import { BiTask } from "react-icons/bi"; import { CiEdit } from "react-icons/ci"; import { FaRegHeart } from "react-icons/fa"; import { IoShareSocialOutline } from "react-icons/io5"; +import { useRecoilState } from "recoil"; import styles from "./MeatballBottomSlide.module.scss"; import CustomToast from "@/components/CustomToast/CustomToast"; import CloseIcon from "@/assets/close.svg?react"; +import { IsHeartValued } from "@/recoil/detail/detail"; import RegistrationSlide from "../../BottomFixedBtn/RegistrationSlide/RegistrationSlide"; import ReviewBottomSlide from "../../Contents/ReviewBottomSlide/ReviewBottomSlide"; @@ -18,8 +20,17 @@ const MeatballBottomSlide = ({ onBottomSlideOpen, onClose, }: NavigationMeatballProps) => { + const [isHeart, setIsHeart] = useRecoilState(IsHeartValued); + const showToast = CustomToast(); + const handleHeartClick = () => { + if (!isHeart) { + showToast("찜 목록에 저장되었습니다."); + } + + setIsHeart(!isHeart); + }; return (
- + }} + > + + 새 투표 생성 + + ) : ( + + )}
여행 스페이스
diff --git a/src/components/Detail/Main/Title/Title.tsx b/src/components/Detail/Main/Title/Title.tsx index f2dc8742..a16c1ab8 100644 --- a/src/components/Detail/Main/Title/Title.tsx +++ b/src/components/Detail/Main/Title/Title.tsx @@ -26,7 +26,7 @@ function Title() { useEffect(() => { setIsHeart(true); - }, []); + }); return (
From 1d4f21ea4816a8dfe211b81cd8a61c9a46eba3d5 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Fri, 19 Jan 2024 10:08:21 +0900 Subject: [PATCH 06/50] Feat: create TripSelected but not valued --- .../RegistrationSlide.module.scss | 15 ++++++ .../RegistrationSlide/RegistrationSlide.tsx | 49 ++++++++++++------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.module.scss b/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.module.scss index 692801e1..6588ef6c 100644 --- a/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.module.scss +++ b/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.module.scss @@ -63,6 +63,21 @@ gap: 8px; } + &__voteNotValued { + margin-top: 64px; + text-align: center; + + &__top { + @include typography(subTitle); + color: $neutral400; + } + + &__bottom { + @include typography(captionSmall); + color: $neutral300; + } + } + &__bottomFixedBtn { padding: 12px 0; width: 100%; diff --git a/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.tsx b/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.tsx index e1fd0b06..e2f08032 100644 --- a/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.tsx +++ b/src/components/Detail/BottomFixedBtn/RegistrationSlide/RegistrationSlide.tsx @@ -24,6 +24,8 @@ function RegistrationSlide({ slideOnClose }: RegistrationSlideProps) { // useEffect fetchData + const exArray = []; + return ( <>
@@ -57,26 +59,37 @@ function RegistrationSlide({ slideOnClose }: RegistrationSlideProps) {
투표리스트
{TripSelected ? ( -
-
- 후보로 등록할 투표 제목을 선택해주세요 + exArray.length > 0 ? ( +
+
+ 후보로 등록할 투표 제목을 선택해주세요 +
+
+ + {/* 초과 글씨 ... 처리 해야함*/} + + +
-
- - {/* 초과 글씨 ... 처리 해야함*/} - - + ) : ( +
+

+ 생성된 투표가 없습니다. +

+

+ 투표 생성을 눌러 새로운 투표를 만들어주세요! +

-
+ ) ) : (
여행을 먼저 선택해주세요 From 6381b6e6f8dd5dc391d21bcac0a059e43a5dc927 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 19 Jan 2024 14:42:08 +0900 Subject: [PATCH 07/50] Feat: add mypage --- src/assets/icons/mapPin.svg | 6 + src/assets/icons/pencil.svg | 5 + src/assets/icons/star.svg | 5 + src/components/Auth/Login/LoginForm.tsx | 12 +- src/components/Auth/Signup/SignupForm.tsx | 1 - src/pages/User/User.module.scss | 142 ++++++++++++++++++++++ src/pages/User/User.tsx | 98 +++++++++++++++ src/routes/MainRouter/MainRouter.tsx | 3 +- 8 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 src/assets/icons/mapPin.svg create mode 100644 src/assets/icons/pencil.svg create mode 100644 src/assets/icons/star.svg create mode 100644 src/pages/User/User.module.scss create mode 100644 src/pages/User/User.tsx diff --git a/src/assets/icons/mapPin.svg b/src/assets/icons/mapPin.svg new file mode 100644 index 00000000..9da4371c --- /dev/null +++ b/src/assets/icons/mapPin.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/pencil.svg b/src/assets/icons/pencil.svg new file mode 100644 index 00000000..197ed04b --- /dev/null +++ b/src/assets/icons/pencil.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/star.svg b/src/assets/icons/star.svg new file mode 100644 index 00000000..869d1899 --- /dev/null +++ b/src/assets/icons/star.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Auth/Login/LoginForm.tsx b/src/components/Auth/Login/LoginForm.tsx index 9bce2cd4..f601a2f0 100644 --- a/src/components/Auth/Login/LoginForm.tsx +++ b/src/components/Auth/Login/LoginForm.tsx @@ -49,10 +49,14 @@ function LoginForm() { if (showError(email as string, password as string)) return; try { - const res = await axios.post("/api/login", { - email, - password, - }); + const res = await axios.post( + "/api/login", + { + email, + password, + }, + { withCredentials: true }, + ); console.log(res.data); navigate("/", { replace: true }); diff --git a/src/components/Auth/Signup/SignupForm.tsx b/src/components/Auth/Signup/SignupForm.tsx index 2f61f998..73cb9bff 100644 --- a/src/components/Auth/Signup/SignupForm.tsx +++ b/src/components/Auth/Signup/SignupForm.tsx @@ -43,7 +43,6 @@ function SignupForm({ signupStep, setSignupStep }: SignupFormProps) { email, password, nickname, - profile: image, token: emailSert, }); console.log(res); diff --git a/src/pages/User/User.module.scss b/src/pages/User/User.module.scss new file mode 100644 index 00000000..fa979cc2 --- /dev/null +++ b/src/pages/User/User.module.scss @@ -0,0 +1,142 @@ +@use "@/sass" as *; + +.container { + width: 100%; + display: flex; + flex-direction: column; + gap: 24px; + color: $neutral900; + + h1 { + padding: 16px 20px; + + @include typography(button); + } + + .profile { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + + &__image { + position: relative; + width: 7.2rem; + height: 7.2rem; + border-radius: 50%; + + background-position: center; + background-repeat: no-repeat; + background-size: cover; + + &__edit { + position: absolute; + bottom: 0; + right: 0; + + background-color: $neutral100; + border: 1px solid $neutral200; + border-radius: 50%; + + width: 2.6rem; + height: 2.6rem; + padding: 4px; + } + } + + &__userName { + @include typography(headline); + } + } + + .mywork { + display: flex; + gap: 8px; + padding: 0 20px; + margin-bottom: 8px; + + button { + border: 1px solid $primary100; + border-radius: 16px; + padding: 16px; + flex: 1; + + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + + &:hover { + border-color: $primary200; + + .myworkTitle { + font-weight: 600; + } + } + } + + .myworkTitle { + @include typography(subTitle); + } + } + + .infoDetail { + li { + display: flex; + justify-content: space-between; + align-items: center; + + padding: 24px 20px; + border-bottom: 1px solid $neutral200; + cursor: pointer; + + @include typography(tabLabel); + + &:hover { + font-weight: 600; + border-color: $neutral300; + } + + svg { + fill: $neutral400; + } + } + + &__alert { + position: relative; + width: 5.6rem; + height: 3.2rem; + border-radius: 32px; + + .alertState { + position: absolute; + top: 0; + width: 3.2rem; + height: 3.2rem; + + background-color: $neutral0; + border-radius: 50%; + transition: all 0.2s ease-in; + } + + &.on { + background-color: $success300; + .alertState { + left: calc(100% - 3.2rem); + border: 2px solid $success300; + } + } + &.off { + background-color: $neutral200; + .alertState { + left: 0; + border: 2px solid $neutral200; + } + } + } + + .version { + color: $neutral400; + } + } +} diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx new file mode 100644 index 00000000..3de65ed4 --- /dev/null +++ b/src/pages/User/User.tsx @@ -0,0 +1,98 @@ +import { useState } from "react"; +import { RiArrowRightSLine } from "react-icons/ri"; +import { useNavigate } from "react-router-dom"; + +import styles from "./User.module.scss"; + +import MapPin from "@/assets/icons/mapPin.svg?react"; +import Pencil from "@/assets/icons/pencil.svg?react"; +import Star from "@/assets/icons/star.svg?react"; + +const userInfo = { + nickname: "개발토끼", + image: "https://avatars.githubusercontent.com/u/100336573?v=4", +}; + +function User() { + // const [userInfo, setUserInfo] = useState({ + // nickname: "", + // profile: profileDefault, + // }); + const [alertOn, setAlertOn] = useState( + Notification.permission === "granted" ? true : false, + ); + const navigate = useNavigate(); + + const onClickPencil = () => { + // 프로필 수정 페이지 이동 + }; + + const onClickAlert = () => { + if (Notification.permission === "denied") { + //브라우저 알림 설정이 해제되어있습니다 alert + return; + } + + setAlertOn(!alertOn); + }; + + return ( +
+

마이페이지

+ +
+
+ +
+ +
{userInfo.nickname}
+
+ +
+ + + +
+ +
    +
  • +
    계정 관리
    + +
  • + +
  • +
    알림
    + +
  • + +
  • +
    버전 정보
    +
    1.1.0
    +
  • +
+
+ ); +} + +export default User; diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index 9f720865..ac0a536d 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -12,6 +12,7 @@ import Home from "@/pages/Home/Home"; import RegionSearch from "@/pages/RegionSearch/RegionSearch"; import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; import Trip from "@/pages/Trip/Trip"; +import User from "@/pages/User/User"; import Vote from "@/pages/Vote/Vote"; import Dashboard from "@/routes/Dashboard/Dashboard"; @@ -22,7 +23,7 @@ function MainRouter() { } /> } /> } /> - } /> + } /> } /> } /> From b4ae6a0ddfc185daa64ac46adc7188a668859051 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 19 Jan 2024 15:22:25 +0900 Subject: [PATCH 08/50] Feat: add userPrivacy --- src/pages/User/User.tsx | 6 ++- .../User/UserPrivacy/UserPrivacy.module.scss | 51 +++++++++++++++++++ src/pages/User/UserPrivacy/UserPrivacy.tsx | 43 ++++++++++++++++ src/routes/MainRouter/MainRouter.tsx | 2 + 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/pages/User/UserPrivacy/UserPrivacy.module.scss create mode 100644 src/pages/User/UserPrivacy/UserPrivacy.tsx diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 3de65ed4..becf072d 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -69,7 +69,11 @@ function User() {
    -
  • +
  • { + navigate("/user/privacy"); + }} + >
    계정 관리
  • diff --git a/src/pages/User/UserPrivacy/UserPrivacy.module.scss b/src/pages/User/UserPrivacy/UserPrivacy.module.scss new file mode 100644 index 00000000..b97ba8a8 --- /dev/null +++ b/src/pages/User/UserPrivacy/UserPrivacy.module.scss @@ -0,0 +1,51 @@ +@use "@/sass" as *; + +.container { + width: 100%; + display: flex; + flex-direction: column; + color: $neutral900; + + .userPrivacy { + margin-top: 56px; + + @include typography(tabLabel); + + li { + padding: 24px 20px; + border-bottom: 1px solid $neutral200; + cursor: pointer; + + &:hover { + font-weight: 600; + border-color: $neutral300; + } + } + + &__email { + display: flex; + flex-direction: column; + gap: 2px; + + small { + @include typography(tabLabel); + color: $neutral400; + } + } + + &__password { + display: flex; + justify-content: space-between; + + a { + @include typography(tabLabel); + color: $primary300; + + &:hover { + color: $primary400; + font-weight: 600; + } + } + } + } +} diff --git a/src/pages/User/UserPrivacy/UserPrivacy.tsx b/src/pages/User/UserPrivacy/UserPrivacy.tsx new file mode 100644 index 00000000..50a310c6 --- /dev/null +++ b/src/pages/User/UserPrivacy/UserPrivacy.tsx @@ -0,0 +1,43 @@ +import { Link } from "react-router-dom"; + +import styles from "./UserPrivacy.module.scss"; + +import Header from "@/components/Auth/Header/Header"; + +const userInfo = { + email: "test@gmail.com", +}; + +function UserPrivacy() { + return ( +
    +
    + +
      +
    • +
      트립보트 계정 이메일
      + {userInfo.email} +
    • + +
    • +
      비밀번호
      + 재설정 +
    • + +
    • { + // 로그아웃 api 호출 (백엔드 미완성) + }} + > + 로그아웃 +
    • + +
    • + 회원 탈퇴 +
    • +
    +
    + ); +} + +export default UserPrivacy; diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index ac0a536d..3c6d4baf 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -13,6 +13,7 @@ import RegionSearch from "@/pages/RegionSearch/RegionSearch"; import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; import Trip from "@/pages/Trip/Trip"; import User from "@/pages/User/User"; +import UserPrivacy from "@/pages/User/UserPrivacy/UserPrivacy"; import Vote from "@/pages/Vote/Vote"; import Dashboard from "@/routes/Dashboard/Dashboard"; @@ -24,6 +25,7 @@ function MainRouter() { } /> } /> } /> + } /> } /> } /> From 1138111e9a29189085ea3b6f5994d2a0939d2bdc Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 19 Jan 2024 16:05:53 +0900 Subject: [PATCH 09/50] Feat: add edit profile page --- src/components/Auth/Input/Input.module.scss | 4 ++ .../EditProfileForm.module.scss | 22 +++++++++ .../User/EditProfileForm/EditProfileForm.tsx | 45 +++++++++++++++++++ .../ModifyProfile/ModifyProfile.module.scss | 5 +++ .../User/ModifyProfile/ModifyProfile.tsx | 16 +++++++ src/pages/User/User.tsx | 8 ++-- src/routes/MainRouter/MainRouter.tsx | 2 + 7 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 src/components/User/EditProfileForm/EditProfileForm.module.scss create mode 100644 src/components/User/EditProfileForm/EditProfileForm.tsx create mode 100644 src/pages/User/ModifyProfile/ModifyProfile.module.scss create mode 100644 src/pages/User/ModifyProfile/ModifyProfile.tsx diff --git a/src/components/Auth/Input/Input.module.scss b/src/components/Auth/Input/Input.module.scss index e3ce9bf3..2ebe0eea 100644 --- a/src/components/Auth/Input/Input.module.scss +++ b/src/components/Auth/Input/Input.module.scss @@ -154,4 +154,8 @@ margin-top: 2px; margin-bottom: 12px; } + + .removeBtn { + top: 81px; + } } diff --git a/src/components/User/EditProfileForm/EditProfileForm.module.scss b/src/components/User/EditProfileForm/EditProfileForm.module.scss new file mode 100644 index 00000000..ea311f5f --- /dev/null +++ b/src/components/User/EditProfileForm/EditProfileForm.module.scss @@ -0,0 +1,22 @@ +@use "@/sass" as *; + +.container { + padding: 0 20px; + + label { + display: block; + + @include typography(tabLabel); + color: $neutral900; + } + + small { + width: 100%; + position: absolute; + bottom: -4px; + left: 0; + + @include typography(captionSmall); + color: $danger300; + } +} diff --git a/src/components/User/EditProfileForm/EditProfileForm.tsx b/src/components/User/EditProfileForm/EditProfileForm.tsx new file mode 100644 index 00000000..3ed0cd0a --- /dev/null +++ b/src/components/User/EditProfileForm/EditProfileForm.tsx @@ -0,0 +1,45 @@ +import { useForm } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; + +import styles from "./EditProfileForm.module.scss"; + +import AuthButton from "@/components/Auth/Button/AuthButton"; +import InputImage from "@/components/Auth/Input/InputImage"; +import InputNickname from "@/components/Auth/Input/InputNickname"; + +import { AuthForm } from "@/types/auth"; + +function EditProfileForm() { + const { + register, + resetField, + formState: { errors, dirtyFields }, + handleSubmit, + } = useForm({ + mode: "onChange", + }); + + const navigate = useNavigate(); + + const onSubmit = () => { + // 프로필 변경 api 요청 + navigate("/user", { replace: true }); + }; + + return ( +
    + + + + + + + ); +} + +export default EditProfileForm; diff --git a/src/pages/User/ModifyProfile/ModifyProfile.module.scss b/src/pages/User/ModifyProfile/ModifyProfile.module.scss new file mode 100644 index 00000000..ab67694d --- /dev/null +++ b/src/pages/User/ModifyProfile/ModifyProfile.module.scss @@ -0,0 +1,5 @@ +@use "@/sass" as *; + +.container { + margin-top: 80px; +} diff --git a/src/pages/User/ModifyProfile/ModifyProfile.tsx b/src/pages/User/ModifyProfile/ModifyProfile.tsx new file mode 100644 index 00000000..c81f6be7 --- /dev/null +++ b/src/pages/User/ModifyProfile/ModifyProfile.tsx @@ -0,0 +1,16 @@ +import styles from "./ModifyProfile.module.scss"; + +import Header from "@/components/Auth/Header/Header"; +import EditProfileForm from "@/components/User/EditProfileForm/EditProfileForm"; + +function ModifyProfile() { + return ( +
    +
    + + +
    + ); +} + +export default ModifyProfile; diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index becf072d..606a1625 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -23,10 +23,6 @@ function User() { ); const navigate = useNavigate(); - const onClickPencil = () => { - // 프로필 수정 페이지 이동 - }; - const onClickAlert = () => { if (Notification.permission === "denied") { //브라우저 알림 설정이 해제되어있습니다 alert @@ -47,7 +43,9 @@ function User() { > diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index 3c6d4baf..c5d480d1 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -12,6 +12,7 @@ import Home from "@/pages/Home/Home"; import RegionSearch from "@/pages/RegionSearch/RegionSearch"; import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; import Trip from "@/pages/Trip/Trip"; +import ModifyProfile from "@/pages/User/ModifyProfile/ModifyProfile"; import User from "@/pages/User/User"; import UserPrivacy from "@/pages/User/UserPrivacy/UserPrivacy"; import Vote from "@/pages/Vote/Vote"; @@ -26,6 +27,7 @@ function MainRouter() { } /> } /> } /> + } /> } /> } /> From 4ff9fbd50cd5dafd9ee6467df122fffbb707598c Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Fri, 19 Jan 2024 16:13:43 +0900 Subject: [PATCH 10/50] Feat: isLogin modal --- .../Detail/BottomFixedBtn/BottomFixedBtn.tsx | 30 +++++++++++++++- .../Information/ShortReveiws/ShortReviews.tsx | 34 +++++++++++++++++-- .../Detail/Contents/Reviews/Reviews.tsx | 34 +++++++++++++++++-- src/components/Detail/Main/Title/Title.tsx | 33 ++++++++++++++---- .../MeatballBottomSlide.tsx | 32 ++++++++++++++--- src/pages/Detail/Detail.tsx | 6 ++++ src/recoil/detail/detail.ts | 5 +++ 7 files changed, 158 insertions(+), 16 deletions(-) diff --git a/src/components/Detail/BottomFixedBtn/BottomFixedBtn.tsx b/src/components/Detail/BottomFixedBtn/BottomFixedBtn.tsx index 3ac4d0b6..1269d840 100644 --- a/src/components/Detail/BottomFixedBtn/BottomFixedBtn.tsx +++ b/src/components/Detail/BottomFixedBtn/BottomFixedBtn.tsx @@ -1,8 +1,30 @@ +import { useRecoilValue, useSetRecoilState } from "recoil"; + import styles from "./BottomFixedBtn.module.scss"; +import { IsLoginState } from "@/recoil/detail/detail"; +import { isModalOpenState, modalContentState } from "@/recoil/vote/alertModal"; + import { BottomFixedBtnProps } from "@/types/detail"; function BottomFixedBtn({ onOpen }: BottomFixedBtnProps) { + const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setModalContent = useSetRecoilState(modalContentState); + const isLogin = useRecoilValue(IsLoginState); + + const notLoginContent = { + title: "로그인이 필요한 기능입니다.", + subText: "로그인하고 모든 서비스를 이용해 보세요! ", + cancelText: "닫기", + actionButton: "로그인하기", + isSmallSize: true, + }; + + const showNotLoginModal = () => { + setIsModalOpen(true); + setModalContent({ ...notLoginContent }); + }; + return (
    { + if (isLogin) { + onOpen(); + } else { + showNotLoginModal(); + } + }} > 이 장소 후보로 등록하기
    diff --git a/src/components/Detail/Contents/Information/ShortReveiws/ShortReviews.tsx b/src/components/Detail/Contents/Information/ShortReveiws/ShortReviews.tsx index 649de686..c2e18d2b 100644 --- a/src/components/Detail/Contents/Information/ShortReveiws/ShortReviews.tsx +++ b/src/components/Detail/Contents/Information/ShortReveiws/ShortReviews.tsx @@ -1,13 +1,34 @@ import { CiEdit } from "react-icons/ci"; import { GoStarFill } from "react-icons/go"; +import { useRecoilValue, useSetRecoilState } from "recoil"; import styles from "./ShortReviews.module.scss"; import Review from "@/components/Detail/Contents/Review/Review"; +import { IsLoginState } from "@/recoil/detail/detail"; +import { isModalOpenState, modalContentState } from "@/recoil/vote/alertModal"; + import { ContentsShortReviewsProps } from "@/types/detail"; function ShortReviews({ onOpen }: ContentsShortReviewsProps) { + const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setModalContent = useSetRecoilState(modalContentState); + const isLogin = useRecoilValue(IsLoginState); + + const notLoginContent = { + title: "로그인이 필요한 기능입니다.", + subText: "로그인하고 모든 서비스를 이용해 보세요! ", + cancelText: "닫기", + actionButton: "로그인하기", + isSmallSize: true, + }; + + const showNotLoginModal = () => { + setIsModalOpen(true); + setModalContent({ ...notLoginContent }); + }; + const reviewData = [ { name: "강자밭", @@ -41,10 +62,19 @@ function ShortReviews({ onOpen }: ContentsShortReviewsProps) {

    리뷰

    -
    +
    +
    diff --git a/src/components/Detail/Contents/Reviews/Reviews.tsx b/src/components/Detail/Contents/Reviews/Reviews.tsx index 67bd1ec6..bf48dbd9 100644 --- a/src/components/Detail/Contents/Reviews/Reviews.tsx +++ b/src/components/Detail/Contents/Reviews/Reviews.tsx @@ -1,13 +1,34 @@ import { CiEdit } from "react-icons/ci"; import { GoStarFill } from "react-icons/go"; +import { useRecoilValue, useSetRecoilState } from "recoil"; import styles from "./Rviews.module.scss"; import Review from "@/components/Detail/Contents/Review/Review"; +import { IsLoginState } from "@/recoil/detail/detail"; +import { isModalOpenState, modalContentState } from "@/recoil/vote/alertModal"; + import { ContentsReviewsProps } from "@/types/detail"; // 무한 스크롤 구현 필요 function Reviews({ onOpen }: ContentsReviewsProps) { + const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setModalContent = useSetRecoilState(modalContentState); + const isLogin = useRecoilValue(IsLoginState); + + const notLoginContent = { + title: "로그인이 필요한 기능입니다.", + subText: "로그인하고 모든 서비스를 이용해 보세요! ", + cancelText: "닫기", + actionButton: "로그인하기", + isSmallSize: true, + }; + + const showNotLoginModal = () => { + setIsModalOpen(true); + setModalContent({ ...notLoginContent }); + }; + const reviewData = [ { name: "강자밭", @@ -118,10 +139,19 @@ function Reviews({ onOpen }: ContentsReviewsProps) {

    리뷰

    -
    +
    +
    diff --git a/src/components/Detail/Main/Title/Title.tsx b/src/components/Detail/Main/Title/Title.tsx index a16c1ab8..af8071f8 100644 --- a/src/components/Detail/Main/Title/Title.tsx +++ b/src/components/Detail/Main/Title/Title.tsx @@ -3,25 +3,46 @@ import { FaRegHeart } from "react-icons/fa"; import { FaHeart } from "react-icons/fa"; import { GoStarFill } from "react-icons/go"; import { IoShareSocialOutline } from "react-icons/io5"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import styles from "./Title.module.scss"; import CustomToast from "@/components/CustomToast/CustomToast"; -import { IsHeartValued } from "@/recoil/detail/detail"; +import { IsHeartValued, IsLoginState } from "@/recoil/detail/detail"; +import { isModalOpenState, modalContentState } from "@/recoil/vote/alertModal"; function Title() { const [isHeart, setIsHeart] = useRecoilState(IsHeartValued); + const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setModalContent = useSetRecoilState(modalContentState); + + const isLogin = useRecoilValue(IsLoginState); + + const notLoginContent = { + title: "로그인이 필요한 기능입니다.", + subText: "로그인하고 모든 서비스를 이용해 보세요! ", + cancelText: "닫기", + actionButton: "로그인하기", + isSmallSize: true, + }; + + const showNotLoginModal = () => { + setIsModalOpen(true); + setModalContent({ ...notLoginContent }); + }; const showToast = CustomToast(); const handleHeartClick = () => { - if (!isHeart) { - showToast("찜 목록에 저장되었습니다."); + if (isLogin) { + if (!isHeart) { + showToast("찜 목록에 저장되었습니다."); + } + setIsHeart(!isHeart); + } else { + showNotLoginModal(); } - - setIsHeart(!isHeart); }; useEffect(() => { diff --git a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx index 7f830fdd..35e2cc39 100644 --- a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx +++ b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx @@ -2,14 +2,15 @@ import { BiTask } from "react-icons/bi"; import { CiEdit } from "react-icons/ci"; import { FaRegHeart } from "react-icons/fa"; import { IoShareSocialOutline } from "react-icons/io5"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; import styles from "./MeatballBottomSlide.module.scss"; import CustomToast from "@/components/CustomToast/CustomToast"; import CloseIcon from "@/assets/close.svg?react"; -import { IsHeartValued } from "@/recoil/detail/detail"; +import { IsHeartValued, IsLoginState } from "@/recoil/detail/detail"; +import { isModalOpenState, modalContentState } from "@/recoil/vote/alertModal"; import RegistrationSlide from "../../BottomFixedBtn/RegistrationSlide/RegistrationSlide"; import ReviewBottomSlide from "../../Contents/ReviewBottomSlide/ReviewBottomSlide"; @@ -21,7 +22,22 @@ const MeatballBottomSlide = ({ onClose, }: NavigationMeatballProps) => { const [isHeart, setIsHeart] = useRecoilState(IsHeartValued); + const setIsModalOpen = useSetRecoilState(isModalOpenState); + const setModalContent = useSetRecoilState(modalContentState); + const isLogin = useRecoilValue(IsLoginState); + const notLoginContent = { + title: "로그인이 필요한 기능입니다.", + subText: "로그인하고 모든 서비스를 이용해 보세요! ", + cancelText: "닫기", + actionButton: "로그인하기", + isSmallSize: true, + }; + + const showNotLoginModal = () => { + setIsModalOpen(true); + setModalContent({ ...notLoginContent }); + }; const showToast = CustomToast(); const handleHeartClick = () => { @@ -31,6 +47,7 @@ const MeatballBottomSlide = ({ setIsHeart(!isHeart); }; + return (
    -
    +
    { + setTabIndex(1); + window.scrollTo(0, tabPosition); + }} + > 리뷰 전체보기
    diff --git a/src/recoil/detail/detail.ts b/src/recoil/detail/detail.ts index 2a2f8644..2447bbed 100644 --- a/src/recoil/detail/detail.ts +++ b/src/recoil/detail/detail.ts @@ -24,3 +24,13 @@ export const IsLoginState = atom({ key: "IsLoginState", default: true, }); + +export const TabIndexState = atom({ + key: "TabIndexState", + default: 0, +}); + +export const TabYPosition = atom({ + key: "TabYPosition", + default: 0, +}); From b807a25019c665d2b5affbb34009f03008b0125b Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Sat, 20 Jan 2024 01:28:13 +0900 Subject: [PATCH 13/50] Feat: add modify password flow --- .../Auth/Input/InputOldPassword.tsx | 33 +++++++++ .../ModifyPasswordForm.module.scss | 10 +++ .../ModifyPassword/ModifyPasswordForm.tsx | 71 +++++++++++++++++++ .../Auth/ModifyPassword/Step/Step.module.scss | 41 +++++++++++ .../ModifyPassword/Step/StepNewPassword.tsx | 51 +++++++++++++ .../ModifyPassword/Step/StepOldPassword.tsx | 59 +++++++++++++++ src/components/Auth/Signup/AgreeForm.tsx | 4 +- src/components/Auth/Signup/SignupForm.tsx | 8 ++- .../Auth/Signup/Step/StepEmailSert.tsx | 5 +- src/pages/Auth/Login/Login.tsx | 4 +- .../ModifyPassword/ModifyPassword.module.scss | 7 ++ .../Auth/ModifyPassword/ModifyPassword.tsx | 18 +++++ src/pages/User/User.tsx | 2 +- src/pages/User/UserPrivacy/UserPrivacy.tsx | 2 +- src/routes/MainRouter/MainRouter.tsx | 10 +-- src/types/auth.ts | 7 ++ 16 files changed, 320 insertions(+), 12 deletions(-) create mode 100644 src/components/Auth/Input/InputOldPassword.tsx create mode 100644 src/components/Auth/ModifyPassword/ModifyPasswordForm.module.scss create mode 100644 src/components/Auth/ModifyPassword/ModifyPasswordForm.tsx create mode 100644 src/components/Auth/ModifyPassword/Step/Step.module.scss create mode 100644 src/components/Auth/ModifyPassword/Step/StepNewPassword.tsx create mode 100644 src/components/Auth/ModifyPassword/Step/StepOldPassword.tsx create mode 100644 src/pages/Auth/ModifyPassword/ModifyPassword.module.scss create mode 100644 src/pages/Auth/ModifyPassword/ModifyPassword.tsx diff --git a/src/components/Auth/Input/InputOldPassword.tsx b/src/components/Auth/Input/InputOldPassword.tsx new file mode 100644 index 00000000..9d768e4b --- /dev/null +++ b/src/components/Auth/Input/InputOldPassword.tsx @@ -0,0 +1,33 @@ +import styles from "./Input.module.scss"; + +import validationForm from "@/utils/inputValidation"; + +function InputOldPassword({ register, dirtyFields, errors }) { + return ( +
    + + + + + {!dirtyFields?.oldPassword || errors?.oldPassword ? ( + {errors?.oldPassword?.message} + ) : null} +
    + ); +} + +export default InputOldPassword; diff --git a/src/components/Auth/ModifyPassword/ModifyPasswordForm.module.scss b/src/components/Auth/ModifyPassword/ModifyPasswordForm.module.scss new file mode 100644 index 00000000..61891890 --- /dev/null +++ b/src/components/Auth/ModifyPassword/ModifyPasswordForm.module.scss @@ -0,0 +1,10 @@ +@use "@/sass" as *; + +.container { + position: relative; + color: $neutral900; + + h2 { + @include typography(headline); + } +} diff --git a/src/components/Auth/ModifyPassword/ModifyPasswordForm.tsx b/src/components/Auth/ModifyPassword/ModifyPasswordForm.tsx new file mode 100644 index 00000000..ee39b1a1 --- /dev/null +++ b/src/components/Auth/ModifyPassword/ModifyPasswordForm.tsx @@ -0,0 +1,71 @@ +import axios from "axios"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; + +import styles from "./ModifyPasswordForm.module.scss"; + +import StepNewPassword from "./Step/StepNewPassword"; +import StepOldPassword from "./Step/StepOldPassword"; + +import { AuthForm } from "@/types/auth"; + +function ModifyPasswordForm() { + const { + register, + resetField, + handleSubmit, + watch, + formState: { errors, dirtyFields }, + } = useForm({ + mode: "onChange", + defaultValues: { + oldPassword: "", + password: "", + passwordConfirm: "", + }, + }); + const watchFields = watch(); + + // steps : oldPassword, newPassword + const [step, setStep] = useState("oldPassword"); + const [token, setToken] = useState(""); + + const onSubmit = async () => { + try { + const res = axios.post("/api/auth/modify/password", { + token, + newPassword: watchFields.password, + }); + + console.log(res); + } catch (error) { + console.log(error); + } + }; + + return ( +
    + {step === "oldPassword" && ( + + )} + + {step === "newPassword" && ( + + )} + + ); +} + +export default ModifyPasswordForm; diff --git a/src/components/Auth/ModifyPassword/Step/Step.module.scss b/src/components/Auth/ModifyPassword/Step/Step.module.scss new file mode 100644 index 00000000..7b195097 --- /dev/null +++ b/src/components/Auth/ModifyPassword/Step/Step.module.scss @@ -0,0 +1,41 @@ +@use "@/sass" as *; + +.container { + position: relative; + + label { + display: block; + + @include typography(tabLabel); + color: $neutral900; + } + + small { + width: 100%; + position: absolute; + bottom: -4px; + left: 0; + + @include typography(captionSmall); + color: $danger300; + + &.step__desc { + position: relative; + margin-top: 8px; + color: $neutral400; + } + } + + .toFindPassword { + display: flex; + justify-content: center; + margin-top: 16px; + + @include typography(captionSmall); + color: $neutral400; + + &:hover { + font-weight: 600; + } + } +} diff --git a/src/components/Auth/ModifyPassword/Step/StepNewPassword.tsx b/src/components/Auth/ModifyPassword/Step/StepNewPassword.tsx new file mode 100644 index 00000000..99bc89dc --- /dev/null +++ b/src/components/Auth/ModifyPassword/Step/StepNewPassword.tsx @@ -0,0 +1,51 @@ +import styles from "./Step.module.scss"; + +import AuthButton from "../../Button/AuthButton"; +import InputPassword from "../../Input/InputPassword"; +import InputPasswordConfirm from "../../Input/InputPasswordConfirm"; + +import { StepPasswordProps } from "@/types/auth"; + +function StepNewPassword({ + register, + resetField, + watchFields: { password, passwordConfirm }, + dirtyFields, + errors, +}: StepPasswordProps) { + return ( +
    +

    새로운 비밀번호를 설정해주세요

    + + + + + + +
    + ); +} + +export default StepNewPassword; diff --git a/src/components/Auth/ModifyPassword/Step/StepOldPassword.tsx b/src/components/Auth/ModifyPassword/Step/StepOldPassword.tsx new file mode 100644 index 00000000..642b7315 --- /dev/null +++ b/src/components/Auth/ModifyPassword/Step/StepOldPassword.tsx @@ -0,0 +1,59 @@ +import axios from "axios"; +import { Link } from "react-router-dom"; + +import styles from "./Step.module.scss"; + +import AuthButton from "../../Button/AuthButton"; +import InputOldPassword from "../../Input/InputOldPassword"; + +import { StepOldPasswordProps } from "@/types/auth"; + +function StepOldPassword({ + register, + dirtyFields, + errors, + setStep, + setToken, +}: StepOldPasswordProps) { + const onClickNext = async () => { + try { + const res = await axios.post("/api/auth/modify/password/check"); + + setToken!(await res.data.data.token); + setStep!("newPassword"); + } catch (error) { + console.log(error); + } + }; + + return ( +
    +

    비밀번호 확인

    + + + 비밀번호 재설정을 위해 현재 비밀번호를 입력해주세요. + + + + + + +
    + 비밀번호를 잊으셨나요? +
    +
    + ); +} + +export default StepOldPassword; diff --git a/src/components/Auth/Signup/AgreeForm.tsx b/src/components/Auth/Signup/AgreeForm.tsx index 300ea930..e0cad767 100644 --- a/src/components/Auth/Signup/AgreeForm.tsx +++ b/src/components/Auth/Signup/AgreeForm.tsx @@ -84,7 +84,7 @@ function AgreeForm({ setSignupStep }: AgreeProps) { required: true, })} /> - +
    @@ -97,7 +97,7 @@ function AgreeForm({ setSignupStep }: AgreeProps) { required: true, })} /> - +
    diff --git a/src/components/Auth/Signup/SignupForm.tsx b/src/components/Auth/Signup/SignupForm.tsx index 73cb9bff..ce18256d 100644 --- a/src/components/Auth/Signup/SignupForm.tsx +++ b/src/components/Auth/Signup/SignupForm.tsx @@ -1,4 +1,5 @@ import axios from "axios"; +import { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; @@ -30,20 +31,22 @@ function SignupForm({ signupStep, setSignupStep }: SignupFormProps) { }, }); + const [code, setCode] = useState(""); const navigate = useNavigate(); const watchFields = watch(); const onSubmit: SubmitHandler = async (data) => { + console.log(code); if (Object.keys(dirtyFields).length < 5) return; console.log(data); try { - const { email, emailSert, password, image, nickname } = data; + const { email, password, image, nickname } = data; const res = await axios.post("/api/auth/register", { email, password, nickname, - token: emailSert, + token: code, }); console.log(res); @@ -88,6 +91,7 @@ function SignupForm({ signupStep, setSignupStep }: SignupFormProps) { watchFields={watchFields} dirty={dirtyFields.emailSert} error={errors.emailSert} + setCode={setCode} /> )} diff --git a/src/components/Auth/Signup/Step/StepEmailSert.tsx b/src/components/Auth/Signup/Step/StepEmailSert.tsx index 1b752070..76218bab 100644 --- a/src/components/Auth/Signup/Step/StepEmailSert.tsx +++ b/src/components/Auth/Signup/Step/StepEmailSert.tsx @@ -15,6 +15,7 @@ function StepEmailSert({ watchFields: { email, emailSert }, dirty, error, + setCode, }: StepEmailSertProps) { const [due, setDue] = useState(1800); const showToast = CustomToast(); @@ -35,7 +36,7 @@ function StepEmailSert({ try { const res = await axios.post("/api/auth/register/check-token", { email, - token: emailSert, + code: emailSert, }); console.log(res); @@ -44,6 +45,8 @@ function StepEmailSert({ return; } + console.log(await res.data.data.token); + setCode!(await res.data.data.token); setSignupStep!("password"); } catch (error) { console.log(error); diff --git a/src/pages/Auth/Login/Login.tsx b/src/pages/Auth/Login/Login.tsx index c4b88cd1..590c25cf 100644 --- a/src/pages/Auth/Login/Login.tsx +++ b/src/pages/Auth/Login/Login.tsx @@ -11,7 +11,9 @@ import Logo from "@/assets/logo.svg?react"; function Login() { const onClickKakao = async () => { try { - const res = axios.post("/api/oauth2/authorization/kakao"); + const res = axios.post("/api/oauth2/authorization/kakao", null, { + withCredentials: true, + }); console.log(res); } catch (error) { if (axios.isAxiosError(error)) { diff --git a/src/pages/Auth/ModifyPassword/ModifyPassword.module.scss b/src/pages/Auth/ModifyPassword/ModifyPassword.module.scss new file mode 100644 index 00000000..95e27d3a --- /dev/null +++ b/src/pages/Auth/ModifyPassword/ModifyPassword.module.scss @@ -0,0 +1,7 @@ +@use "@/sass" as *; + +.container { + .body { + padding: 80px 24px; + } +} diff --git a/src/pages/Auth/ModifyPassword/ModifyPassword.tsx b/src/pages/Auth/ModifyPassword/ModifyPassword.tsx new file mode 100644 index 00000000..4333f533 --- /dev/null +++ b/src/pages/Auth/ModifyPassword/ModifyPassword.tsx @@ -0,0 +1,18 @@ +import styles from "./ModifyPassword.module.scss"; + +import Header from "@/components/Auth/Header/Header"; +import ModifyPasswordForm from "@/components/Auth/ModifyPassword/ModifyPasswordForm"; + +function ModifyPassword() { + return ( +
    +
    + +
    + +
    +
    + ); +} + +export default ModifyPassword; diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 606a1625..37072f76 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -44,7 +44,7 @@ function User() {
    {!onboarding && } - {modal && ( - - )} + {modal && }
    ); } diff --git a/src/pages/SearchFromHome/SearchFromHome.module.scss b/src/pages/SearchFromHome/SearchFromHome.module.scss index f85f38d9..b5434c48 100644 --- a/src/pages/SearchFromHome/SearchFromHome.module.scss +++ b/src/pages/SearchFromHome/SearchFromHome.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -6,7 +6,7 @@ display: flex; flex-direction: column; - font-family: "suit", sans-serif; + font-family: 'suit', sans-serif; color: $neutral900; From ba3652d14dd27c5bc2895a310fc0086aeeafc907 Mon Sep 17 00:00:00 2001 From: Yamyam-code Date: Sat, 20 Jan 2024 04:41:09 +0900 Subject: [PATCH 15/50] Feat: add location sort query --- .../SearchFromHome/MapHeader/MapHeader.tsx | 18 ++--- .../SearchFromHome/SearchBar/SearchBar.tsx | 47 +++++++------ .../SearchFromHome/SearchHome/SearchHome.tsx | 14 ++-- .../SearchKeyword/SearchKeyword.tsx | 30 ++++----- .../LocationFilter/LocationFilter.tsx | 11 ++- .../LocationFliterPage/LocationFliterPage.tsx | 12 +++- .../SearchFromHome/SearchList/SearchList.tsx | 52 +++++++++----- .../SearchList/Tabs/Tab/Tab.tsx | 23 ++++--- .../SearchFromHome/SearchList/Tabs/Tabs.tsx | 42 +++++------- src/pages/SearchFromHome/SearchFromHome.tsx | 67 +++++++++++++------ 10 files changed, 187 insertions(+), 129 deletions(-) diff --git a/src/components/SearchFromHome/MapHeader/MapHeader.tsx b/src/components/SearchFromHome/MapHeader/MapHeader.tsx index f1381cdb..5bf2c899 100644 --- a/src/components/SearchFromHome/MapHeader/MapHeader.tsx +++ b/src/components/SearchFromHome/MapHeader/MapHeader.tsx @@ -1,21 +1,23 @@ -import { useNavigate } from "react-router-dom"; +import {useNavigate} from 'react-router-dom'; -import styles from "./MapHeader.module.scss"; +import styles from './MapHeader.module.scss'; -import BackIcon from "@/assets/homeIcons/search/backInHome.svg?react"; +import BackIcon from '@/assets/homeIcons/search/backInHome.svg?react'; interface PropsType { - keyword: string | undefined; + keyword: string; category: string; - set: React.Dispatch>; + searchLocation: string; + sort: string; + setMoveMap: React.Dispatch>; } -function MapHeader({ keyword, set, category }: PropsType) { +function MapHeader({keyword, setMoveMap, category, searchLocation, sort}: PropsType) { const navigate = useNavigate(); function offMap() { - set("false"); - navigate(`/home/search?keyword=${keyword}&category=${category}`); + setMoveMap('false'); + navigate(`/home/search?keyword=${keyword}&category=${category}&map=false&location=${searchLocation}&sort=${sort}`); } return ( diff --git a/src/components/SearchFromHome/SearchBar/SearchBar.tsx b/src/components/SearchFromHome/SearchBar/SearchBar.tsx index 18866fe5..895e5062 100644 --- a/src/components/SearchFromHome/SearchBar/SearchBar.tsx +++ b/src/components/SearchFromHome/SearchBar/SearchBar.tsx @@ -1,29 +1,30 @@ -import { useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import {useEffect, useRef, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; -import styles from "./SearchBar.module.scss"; +import styles from './SearchBar.module.scss'; -import BackIcon from "@/assets/homeIcons/search/backInHome.svg?react"; -import SearchIcon from "@/assets/homeIcons/search/searchIcon.svg?react"; +import BackIcon from '@/assets/homeIcons/search/backInHome.svg?react'; +import SearchIcon from '@/assets/homeIcons/search/searchIcon.svg?react'; interface PropsType { - set: React.Dispatch>; - keyword: string | undefined; + keyword: string; + category: string; + searchLocation: string; + sort: string; + setKeyword: React.Dispatch>; } interface InputBarType extends HTMLInputElement { focus: () => void; } -function SearchBar({ set, keyword }: PropsType) { - const [inputValue, setInputValue] = useState(""); +function SearchBar({setKeyword, keyword, category, searchLocation, sort}: PropsType) { + const [inputValue, setInputValue] = useState(''); const navigate = useNavigate(); const inputBar = useRef(null); useEffect(() => { - if (keyword) { - setInputValue(keyword); - } + setInputValue(keyword); if (inputBar.current) { inputBar.current.focus(); } @@ -34,17 +35,19 @@ function SearchBar({ set, keyword }: PropsType) { } function search() { - set(inputValue); - navigate(`/home/search?keyword=${inputValue}&category=전체`); + setKeyword(inputValue); + navigate( + `/home/search?keyword=${inputValue}&category=${category}&map=false&location=${searchLocation}&sort=${sort}`, + ); } function removeValue() { - if (!keyword) { - navigate("/"); + if (keyword === '') { + navigate('/'); } else { - navigate("/home/search"); - setInputValue(""); - set(undefined); + navigate('/home/search'); + setInputValue(''); + setKeyword(''); } } @@ -53,13 +56,13 @@ function SearchBar({ set, keyword }: PropsType) {
    { - if (e.key === "Enter") { + if (e.key === 'Enter') { search(); } }} diff --git a/src/components/SearchFromHome/SearchHome/SearchHome.tsx b/src/components/SearchFromHome/SearchHome/SearchHome.tsx index b77108bc..45330897 100644 --- a/src/components/SearchFromHome/SearchHome/SearchHome.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchHome.tsx @@ -1,13 +1,13 @@ -import styles from "./SearchHome.module.scss"; +import styles from './SearchHome.module.scss'; -import HotItems from "./HotItems/HotItems"; -import SearchKeyword from "./SearchKeyword/SearchKeyword"; +import HotItems from './HotItems/HotItems'; +import SearchKeyword from './SearchKeyword/SearchKeyword'; interface PropsType { - set: React.Dispatch>; + set: React.Dispatch>; } -function SearchHome({ set }: PropsType) { +function SearchHome({set}: PropsType) { return (
    @@ -16,11 +16,11 @@ function SearchHome({ set }: PropsType) {

    최근 30일간 인기 장소

    - +

    최근 30일간 인기 숙소

    - +
    ); diff --git a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx index 7f4a73da..ba80a087 100644 --- a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx @@ -1,19 +1,19 @@ -import { useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import {useEffect, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; -import styles from "./SearchKeyword.module.scss"; +import styles from './SearchKeyword.module.scss'; -import useComponentSize from "@/hooks/useComponetSize"; +import useComponentSize from '@/hooks/useComponetSize'; -import SlideButton from "@/components/SlideButton/SlideButton"; +import SlideButton from '@/components/SlideButton/SlideButton'; -import { getData } from "@/mocks/handlers/home"; +import {getData} from '@/mocks/handlers/home'; interface PropsType { - set: React.Dispatch>; + set: React.Dispatch>; } -function SearchKeyword({ set }: PropsType) { +function SearchKeyword({set}: PropsType) { const [data, setData] = useState(); const [listWidth, setListWidth] = useState(0); const [slideLocation, setSlideLocation] = useState(0); @@ -21,19 +21,15 @@ function SearchKeyword({ set }: PropsType) { const navigate = useNavigate(); useEffect(() => { - getData("home/search/keyword", setData); + getData('home/search/keyword', setData); }, []); // 각 키워드의 너비를 모두 더한 값을 구함 useEffect(() => { // 정확한 이유를 찾지 못하였으나 setTimeout을 걸지 않으면 각 p태그의 width가 실제보다 다소 큰 수가 반영됨 setTimeout(() => { - if ( - data && - componentRef.current && - componentRef.current?.childNodes.length === data.length - ) { - const pTags = componentRef.current.querySelectorAll("p"); + if (data && componentRef.current && componentRef.current?.childNodes.length === data.length) { + const pTags = componentRef.current.querySelectorAll('p'); const widths = Array.from(pTags).map((pTag) => { const rect = pTag.getBoundingClientRect(); return rect.width; @@ -65,8 +61,8 @@ function SearchKeyword({ set }: PropsType) { className={styles.slide_box} ref={componentRef} style={{ - overflow: size.width < 449 ? "scroll" : "visible", - left: slideLocation + "px", + overflow: size.width < 449 ? 'scroll' : 'visible', + left: slideLocation + 'px', }} > {data ? ( diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx index bba7d4a7..9e4cfcfa 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx @@ -6,11 +6,15 @@ import styles from './LocationFilter.module.scss'; import LocationFliterPage from './LocationFliterPage/LocationFliterPage'; interface PropsType { + keyword: string; + category: string; + moveMap: string; searchLocation: string; + sort: string; setSearchLocation: React.Dispatch>; } -function LocationFilter({searchLocation, setSearchLocation}: PropsType) { +function LocationFilter({keyword, category, moveMap, searchLocation, sort, setSearchLocation}: PropsType) { const [click, setClick] = useState(true); const [buttonName, setButtonName] = useState('전체 지역'); @@ -43,7 +47,10 @@ function LocationFilter({searchLocation, setSearchLocation}: PropsType) {
    diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx index 3b2ca98e..37660d7f 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx @@ -1,4 +1,5 @@ import {useState} from 'react'; +import {useNavigate} from 'react-router-dom'; import styles from './LocationFliterPage.module.scss'; @@ -9,7 +10,10 @@ import SelectLocation from './SelectLocation/SelectLocation'; interface PropsType { click: boolean; - searchLocation: string; + keyword: string; + category: string; + moveMap: string; + sort: string; handleClick: () => void; setSearchLocation: React.Dispatch>; } @@ -19,16 +23,20 @@ interface AreaDataType { sigunguCode: number; } -function LocationFliterPage({click, handleClick, setSearchLocation}: PropsType) { +function LocationFliterPage({click, keyword, category, moveMap, sort, handleClick, setSearchLocation}: PropsType) { const [area, setArea] = useState('전국'); const [areaData, setAreaData] = useState(); const [sigungu, setSigungu] = useState('전체 지역'); const [pick, setPick] = useState(''); + const navigate = useNavigate(); const vh = window.innerHeight / 100; function submit() { setSearchLocation(`${area} ${sigungu}`); + navigate( + `/home/search?keyword=${keyword}&category=${category}&map=${moveMap}&location=${area} ${sigungu}&sort=${sort}`, + ); handleClick(); } diff --git a/src/components/SearchFromHome/SearchList/SearchList.tsx b/src/components/SearchFromHome/SearchList/SearchList.tsx index 843985a2..f553de47 100644 --- a/src/components/SearchFromHome/SearchList/SearchList.tsx +++ b/src/components/SearchFromHome/SearchList/SearchList.tsx @@ -15,25 +15,34 @@ import Tabs from './Tabs/Tabs'; import {SearchItemType} from '@/types/home'; interface PropsType { - keyword: string | undefined; + keyword: string; category: string; moveMap: string; - set: React.Dispatch>; + searchLocation: string; + sort: string; + setMoveMap: React.Dispatch>; setCategory: React.Dispatch>; + setSearchLocation: React.Dispatch>; + setSort: React.Dispatch>; } -function SearchList({keyword, set, moveMap, category, setCategory}: PropsType) { +function SearchList({ + keyword, + moveMap, + category, + searchLocation, + sort, + setMoveMap, + setCategory, + setSearchLocation, + setSort, +}: PropsType) { const [data, setData] = useState(); const [filterData, setFilterData] = useState(); const [categoryChange, setCategoryChange] = useState(false); - const [searchLocation, setSearchLocation] = useState('전국'); const navigate = useNavigate(); const [searchParams] = useSearchParams(); - useEffect(() => { - console.log(searchLocation); - }, [searchLocation]); - useEffect(() => { getData('home/search/search', setData); @@ -60,23 +69,34 @@ function SearchList({keyword, set, moveMap, category, setCategory}: PropsType) { }, [data, category]); function onMap() { - set('true'); - if (category) { - navigate(`/home/search?keyword=${keyword}&category${category}&map=true`); - } else { - navigate(`/home/search?keyword=${keyword}&map=true`); - } + setMoveMap('true'); + navigate(`/home/search?keyword=${keyword}&category=${category}&map=true&location=${searchLocation}&sort=${sort}`); } return (
    - + {moveMap === 'true' && filterData ? ( ) : ( <>
    - +
      diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx index 931c4400..669b58e3 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx @@ -1,13 +1,16 @@ -import { useNavigate, useSearchParams } from "react-router-dom"; +import {useNavigate} from 'react-router-dom'; -import styles from "./Tab.module.scss"; +import styles from './Tab.module.scss'; interface PropsType { setCategory: React.Dispatch>; setCategoryChange: React.Dispatch>; category: string; thisCategory: string; - keyword: string | undefined; + keyword: string; + moveMap: string; + searchLocation: string; + sort: string; } function Tab({ @@ -16,8 +19,10 @@ function Tab({ category, thisCategory, keyword, + moveMap, + sort, + searchLocation, }: PropsType) { - const [searchParams] = useSearchParams(); const navigate = useNavigate(); function handleCategory(key: string) { @@ -26,11 +31,7 @@ function Tab({ setCategoryChange(false); }, 150); setCategory(key); - if (searchParams.get("map")) { - navigate(`/home/search?keyword=${keyword}&category=${key}&map=true`); - } else { - navigate(`/home/search?keyword=${keyword}&category=${key}`); - } + navigate(`/home/search?keyword=${keyword}&category=${key}&map=${moveMap}&location=${searchLocation}&sort=${sort}`); } return ( @@ -38,8 +39,8 @@ function Tab({ className={styles.container} id={thisCategory} style={{ - color: category === thisCategory ? "#1d2433" : "#cdcfd0", - borderBottom: category === thisCategory ? "2px solid #1d2433" : "none", + color: category === thisCategory ? '#1d2433' : '#cdcfd0', + borderBottom: category === thisCategory ? '2px solid #1d2433' : 'none', }} onClick={() => { handleCategory(thisCategory); diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx index b2c3f058..8340dd76 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx @@ -1,37 +1,28 @@ -import { useState } from "react"; +import {useState} from 'react'; -import styles from "./Tabs.module.scss"; +import styles from './Tabs.module.scss'; -import useComponentSize from "@/hooks/useComponetSize"; +import useComponentSize from '@/hooks/useComponetSize'; -import SlideButton from "@/components/SlideButton/SlideButton"; +import SlideButton from '@/components/SlideButton/SlideButton'; -import Tab from "./Tab/Tab"; +import Tab from './Tab/Tab'; interface PropsType { + keyword: string; + category: string; + moveMap: string; + searchLocation: string; + sort: string; + setCategory: React.Dispatch>; setCategoryChange: React.Dispatch>; - category: string; - keyword: string | undefined; } -function Tabs({ - setCategory, - setCategoryChange, - category, - keyword, -}: PropsType) { +function Tabs({setCategory, setCategoryChange, category, keyword, moveMap, searchLocation, sort}: PropsType) { const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); - const thisCategory = [ - "전체", - "맛집", - "숙소", - "관광지", - "문화시설", - "레포츠", - "쇼핑", - ]; + const thisCategory = ['전체', '맛집', '숙소', '관광지', '문화시설', '레포츠', '쇼핑']; return (
      @@ -48,8 +39,8 @@ function Tabs({ className={styles.tabs} ref={componentRef} style={{ - overflow: size.width < 449 ? "scroll" : "visible", - left: slideLocation + "px", + overflow: size.width < 449 ? 'scroll' : 'visible', + left: slideLocation + 'px', }} > {thisCategory.map((thisCategory) => ( @@ -60,6 +51,9 @@ function Tabs({ thisCategory={thisCategory} key={thisCategory} keyword={keyword} + moveMap={moveMap} + searchLocation={searchLocation} + sort={sort} /> ))}
      diff --git a/src/pages/SearchFromHome/SearchFromHome.tsx b/src/pages/SearchFromHome/SearchFromHome.tsx index cc352fcb..ff0cd792 100644 --- a/src/pages/SearchFromHome/SearchFromHome.tsx +++ b/src/pages/SearchFromHome/SearchFromHome.tsx @@ -1,26 +1,31 @@ -import { useEffect, useState } from "react"; -import { useSearchParams } from "react-router-dom"; +import {useEffect, useState} from 'react'; +import {useSearchParams} from 'react-router-dom'; -import styles from "./SearchFromHome.module.scss"; +import styles from './SearchFromHome.module.scss'; -import MapHeader from "@/components/SearchFromHome/MapHeader/MapHeader"; -import SearchBar from "@/components/SearchFromHome/SearchBar/SearchBar"; -import SearchHome from "@/components/SearchFromHome/SearchHome/SearchHome"; -import SearchList from "@/components/SearchFromHome/SearchList/SearchList"; +import MapHeader from '@/components/SearchFromHome/MapHeader/MapHeader'; +import SearchBar from '@/components/SearchFromHome/SearchBar/SearchBar'; +import SearchHome from '@/components/SearchFromHome/SearchHome/SearchHome'; +import SearchList from '@/components/SearchFromHome/SearchList/SearchList'; function SearchFromHome() { - const [keyword, setKeyword] = useState(); - const [category, setCategory] = useState("전체"); - const [moveMap, setMoveMap] = useState("false"); + const [keyword, setKeyword] = useState(''); + const [category, setCategory] = useState('전체'); + const [moveMap, setMoveMap] = useState('false'); + const [searchLocation, setSearchLocation] = useState('전국'); + const [sort, setSort] = useState('등록순'); const [searchParams] = useSearchParams(); const vh = window.innerHeight; useEffect(() => { const querystring = { - keyword: searchParams.get("keyword"), - category: searchParams.get("category"), - map: searchParams.get("map"), + keyword: searchParams.get('keyword'), + category: searchParams.get('category'), + map: searchParams.get('map'), + location: searchParams.get('location'), + sort: searchParams.get('sort'), }; + if (querystring.keyword) { setKeyword(querystring.keyword); } @@ -30,6 +35,12 @@ function SearchFromHome() { if (querystring.map) { setMoveMap(querystring.map); } + if (querystring.location) { + setSearchLocation(querystring.location); + } + if (querystring.sort) { + setSort(querystring.sort); + } }, [searchParams]); return ( @@ -37,24 +48,40 @@ function SearchFromHome() { className={styles.container} style={{ height: `${vh}px`, - gap: moveMap === "true" ? "0" : "24px", - paddingTop: moveMap === "true" ? "0" : "16px", + gap: moveMap === 'true' ? '0' : '24px', + paddingTop: moveMap === 'true' ? '0' : '16px', }} > - {moveMap === "true" ? ( - + {moveMap === 'true' ? ( + ) : ( - + )} - {!keyword ? ( + {keyword === '' ? ( ) : ( )}
    From 290f411c160283e6371b869a7fc9e6acef703334 Mon Sep 17 00:00:00 2001 From: Yamyam-code Date: Sat, 20 Jan 2024 18:10:25 +0900 Subject: [PATCH 16/50] Feat: translate query data --- .../RecommendedItem/RecommendedItem.tsx | 23 ++++------ .../RecommendedItemList.tsx | 3 -- .../SearchKeyword/SearchKeyword.tsx | 2 +- src/hooks/Search/useSearch.ts | 43 +++++++++++++++++++ src/pages/SearchFromHome/SearchFromHome.tsx | 11 +++++ src/utils/areas.json | 37 ++++++++-------- 6 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 src/hooks/Search/useSearch.ts diff --git a/src/components/Home/RecommendedItemList/RecommendedItem/RecommendedItem.tsx b/src/components/Home/RecommendedItemList/RecommendedItem/RecommendedItem.tsx index 08f7ce95..c5830c0a 100644 --- a/src/components/Home/RecommendedItemList/RecommendedItem/RecommendedItem.tsx +++ b/src/components/Home/RecommendedItemList/RecommendedItem/RecommendedItem.tsx @@ -1,28 +1,21 @@ -import { FaStar } from "react-icons/fa"; -import { Link } from "react-router-dom"; +import {FaStar} from 'react-icons/fa'; +import {Link} from 'react-router-dom'; -import styles from "./RecommendedItem.module.scss"; +import styles from './RecommendedItem.module.scss'; -import areas from "@/utils/areas.json"; +import areas from '@/utils/areas.json'; -import { RecommendedItemDataType } from "@/types/home"; +import {RecommendedItemDataType} from '@/types/home'; interface PropsType { data: RecommendedItemDataType; } -function RecommendedItem({ data }: PropsType) { - const location = areas.filter((area) => area.areaCode === data.areaCode)[0] - .name; - console.log(data.thumbnail); - +function RecommendedItem({data}: PropsType) { + const location = areas.filter((area) => area.areaCode === data.areaCode)[0].name; return ( - {`${data.title}의 + {`${data.title}의
    {data.title} {location} diff --git a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx index 83e54a82..bf4d6690 100644 --- a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx +++ b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx @@ -8,14 +8,11 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import RecommendedItem from './RecommendedItem/RecommendedItem'; -import {RecommendedItemDataType} from '@/types/home'; - interface PropsType { apiNum: number; } function RecommendedItemList({apiNum}: PropsType) { - const [data, setData] = useState(); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); diff --git a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx index ba80a087..e836eaef 100644 --- a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx @@ -71,7 +71,7 @@ function SearchKeyword({set}: PropsType) { key={keyword + i} onClick={() => { searchKeyword(keyword); - navigate(`/home/search?keyword=${keyword}&category=전체`); + navigate(`/home/search?keyword=${keyword}&category=전체&map=false&location=전국&sort=등록순`); }} > {keyword} diff --git a/src/hooks/Search/useSearch.ts b/src/hooks/Search/useSearch.ts new file mode 100644 index 00000000..ff3521a9 --- /dev/null +++ b/src/hooks/Search/useSearch.ts @@ -0,0 +1,43 @@ +import areaData from '@/utils/areas.json'; + +function translateLocation(location: string) { + const searchLocation = location.split(' '); + let areaCode = 0; + let sigunguCode = 0; + if (searchLocation[0] === '전국') { + return {areaCode, sigunguCode}; + } + const area = areaData.filter((area) => area.name === searchLocation[0])[0]; + areaCode = area.areaCode; + sigunguCode = area.districts.filter((sigungu) => sigungu.name === searchLocation[1])[0].sigunguCode; + return {areaCode, sigunguCode}; +} + +function translateSort(sort: string) { + let sortCode; + switch (sort) { + case '등록순': + sortCode = 'R'; + break; + case '인기순': + sortCode = 'Q'; + break; + case '이름순': + sortCode = 'O'; + break; + + default: + sortCode = 'R'; + } + return sortCode; +} + +export function search(keyword: string, location: string, sort: string) { + const inputData = { + keyword: keyword, + location: translateLocation(location), + sort: translateSort(sort), + }; + + return inputData; +} diff --git a/src/pages/SearchFromHome/SearchFromHome.tsx b/src/pages/SearchFromHome/SearchFromHome.tsx index ff0cd792..adcbc086 100644 --- a/src/pages/SearchFromHome/SearchFromHome.tsx +++ b/src/pages/SearchFromHome/SearchFromHome.tsx @@ -3,6 +3,8 @@ import {useSearchParams} from 'react-router-dom'; import styles from './SearchFromHome.module.scss'; +import {search} from '@/hooks/Search/useSearch'; + import MapHeader from '@/components/SearchFromHome/MapHeader/MapHeader'; import SearchBar from '@/components/SearchFromHome/SearchBar/SearchBar'; import SearchHome from '@/components/SearchFromHome/SearchHome/SearchHome'; @@ -26,7 +28,12 @@ function SearchFromHome() { sort: searchParams.get('sort'), }; + let queryKeyword = ''; + let queryLocation = '전국'; + let querySort = '등록순'; + if (querystring.keyword) { + queryKeyword = querystring.keyword; setKeyword(querystring.keyword); } if (querystring.category) { @@ -36,11 +43,15 @@ function SearchFromHome() { setMoveMap(querystring.map); } if (querystring.location) { + queryLocation = querystring.location; setSearchLocation(querystring.location); } if (querystring.sort) { + querySort = querystring.sort; setSort(querystring.sort); } + + console.log(search(queryKeyword, queryLocation, querySort)); }, [searchParams]); return ( diff --git a/src/utils/areas.json b/src/utils/areas.json index f1ba607a..2b0b61f0 100644 --- a/src/utils/areas.json +++ b/src/utils/areas.json @@ -1,13 +1,14 @@ [ { "name": "전국", - "districts": [{ "name": "전체 지역", "sigunguCode": 1253153514 }] + "areaCode": 0, + "districts": [{"name": "전체 지역", "sigunguCode": 0}] }, { "name": "서울", "areaCode": 1, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "강남구", "sigunguCode": 1 @@ -114,7 +115,7 @@ "name": "인천", "areaCode": 2, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "강화군", "sigunguCode": 1 @@ -161,7 +162,7 @@ "name": "대전", "areaCode": 3, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "대덕구", "sigunguCode": 1 @@ -188,7 +189,7 @@ "name": "대구", "areaCode": 4, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "남구", "sigunguCode": 1 @@ -231,7 +232,7 @@ "name": "광주", "areaCode": 5, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "광산구", "sigunguCode": 1 @@ -258,7 +259,7 @@ "name": "부산", "areaCode": 6, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "강서구", "sigunguCode": 1 @@ -329,7 +330,7 @@ "name": "울산", "areaCode": 7, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "중구", "sigunguCode": 1 @@ -356,7 +357,7 @@ "name": "세종", "areaCode": 8, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "세종특별자치시", "sigunguCode": 1 @@ -367,7 +368,7 @@ "name": "경기", "areaCode": 31, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "가평군", "sigunguCode": 1 @@ -498,7 +499,7 @@ "name": "강원", "areaCode": 32, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "강릉시", "sigunguCode": 1 @@ -577,7 +578,7 @@ "name": "충북", "areaCode": 33, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "괴산군", "sigunguCode": 1 @@ -632,7 +633,7 @@ "name": "충남", "areaCode": 34, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "공주시", "sigunguCode": 1 @@ -699,7 +700,7 @@ "name": "경북", "areaCode": 35, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "경산시", "sigunguCode": 1 @@ -794,7 +795,7 @@ "name": "경남", "areaCode": 36, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "거제시", "sigunguCode": 1 @@ -881,7 +882,7 @@ "name": "전북", "areaCode": 37, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "고창군", "sigunguCode": 1 @@ -944,7 +945,7 @@ "name": "전남", "areaCode": 38, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "고흥군", "sigunguCode": 1 @@ -1039,7 +1040,7 @@ "name": "제주", "areaCode": 39, "districts": [ - { "name": "전체", "sigunguCode": 54314583 }, + {"name": "전체", "sigunguCode": 0}, { "name": "남제주군", "sigunguCode": 1 From 63b25cfc4bfd8545da1f060bd51f799ed74712af Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sat, 20 Jan 2024 19:00:59 +0900 Subject: [PATCH 17/50] Feat: set react-query before connect api --- src/api/vote.ts | 21 +++-- .../CreatVoteTitleModal.tsx | 23 +++-- src/components/VoteTabPanel/VoteTabPanel.tsx | 19 ++--- src/hooks/Votes/vote.ts | 54 ++++-------- src/mocks/handlers/vote.ts | 56 ++++-------- src/mocks/jest/postNewVote.test.ts | 85 +++++++++++++++++++ src/pages/CandidatesMap/CandidatesMap.tsx | 21 ++--- src/pages/Vote/Vote.tsx | 55 ++++++------ src/pages/Vote/VoteMemo/VoteMemo.tsx | 47 +++++----- src/types/vote.ts | 27 +----- 10 files changed, 210 insertions(+), 198 deletions(-) create mode 100644 src/mocks/jest/postNewVote.test.ts diff --git a/src/api/vote.ts b/src/api/vote.ts index 0b4a812f..a6be7453 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -1,25 +1,30 @@ import axios from 'axios'; -import {postTaglineProps, postVoteTitleProps, VoteListInfo} from '@/types/vote'; +import {postTaglineProps, postVoteTitleProps, VoteInfo, VoteListInfo} from '@/types/vote'; //단일vote -export const getVoteInfo = async (voteId: string) => { +export const getVoteInfo = async (voteId: number): Promise => { const response = await axios.get(`/api/votes/${voteId}`); - return response.data.data; + return response.data; }; //voteList in space -export const getVoteListInfo = async (spaceId: string): Promise => { +export const getVoteListInfo = async (spaceId: number): Promise => { const response = await axios.get(`/api/votes/${spaceId}`); - return response.data.data; + return response.data; }; //getVotesResults //vote 추가 -export const PostNewVote = async ({spaceId, title}: postVoteTitleProps): Promise => { - const response = await axios.post('/api/votes', {params: {spaceId, title}}); - return response.data; +export const PostNewVote = async ({spaceId, title}: postVoteTitleProps) => { + try { + const response = await axios.post('/api/votes', {params: {spaceId, title}}); + console.log('response', response); + return response; + } catch (error) { + console.error(error); + } }; //후보 메모 후 추가 diff --git a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx index 36f96bc5..bcdcb031 100644 --- a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx +++ b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx @@ -14,23 +14,32 @@ import { useDisclosure, } from '@chakra-ui/react'; import {useState} from 'react'; +import {useLocation} from 'react-router-dom'; import styles from './CreatVoteTitleModal.module.scss'; +import {usePostNewVote} from '@/hooks/Votes/vote'; + import VoteIcon from '@/assets/voteIcons/ic_vote.svg?react'; const CreatVoteTitleModal = () => { const {isOpen, onOpen, onClose} = useDisclosure(); - const [inputCount, setInputCount] = useState(0); + const location = useLocation(); + const spaceId = Number(location.pathname.split('/')[2]); + const [inputValue, setInputValue] = useState(''); + const postNewVoteMutation = usePostNewVote(); // const navigate = useNavigate(); const newClose = () => { - setInputCount(0); + setInputValue(''); onClose(); }; - const createNewVote = () => { + const handleCreateVote = (inputValue: string) => { + const res = postNewVoteMutation.mutate({spaceId: 1, title: inputValue}); newClose(); + + console.log('res', res); // navigate(`/vote/${data.id}`) }; @@ -60,7 +69,7 @@ const CreatVoteTitleModal = () => { setInputCount(e.target.value.length)} + onChange={(e) => setInputValue(e.target.value)} maxLength={15} borderColor='neutral.800' focusBorderColor='primary.300' @@ -76,7 +85,7 @@ const CreatVoteTitleModal = () => { mt='4px' mr='-1px' > - {inputCount}/15자 + {inputValue.length}/15자 @@ -84,11 +93,11 @@ const CreatVoteTitleModal = () => { diff --git a/src/components/VoteTabPanel/VoteTabPanel.tsx b/src/components/VoteTabPanel/VoteTabPanel.tsx index 81f8353e..6d268fe6 100644 --- a/src/components/VoteTabPanel/VoteTabPanel.tsx +++ b/src/components/VoteTabPanel/VoteTabPanel.tsx @@ -1,22 +1,17 @@ import {Tab, TabList, TabPanel, TabPanels, Tabs} from '@chakra-ui/react'; -import {useEffect, useState} from 'react'; +import {useParams} from 'react-router-dom'; import styles from './VoteTabPanel.module.scss'; -import {getVoteListData} from '@/mocks/handlers/vote'; +import {useGetVoteListInfo} from '@/hooks/Votes/vote'; import TabsVoteCard from './TabsVoteCard/TabsVoteCard'; import VoteTabPanelEmpty from './VoteTabPanelEmpty/VoteTabPanelEmpty'; import CreatVoteTitleModal from '../Vote/CreatVoteTitleModal/CreatVoteTitleModal'; -import {VoteListInfo} from '@/types/vote'; - const VoteTabPanel = () => { - const [data, setData] = useState([]); - - useEffect(() => { - getVoteListData(setData); - }, []); + const {id: voteId} = useParams(); + const {data: voteInfo} = useGetVoteListInfo(Number(voteId)); return (
    @@ -27,11 +22,13 @@ const VoteTabPanel = () => { 진행 중 결정 완료 -

    총 {data.length}개

    +

    총 {voteInfo.length}개

    - {data && data.map((item) => )} + {voteInfo.map((item) => ( + + ))} {/* // : ( // diff --git a/src/hooks/Votes/vote.ts b/src/hooks/Votes/vote.ts index a026754a..fe712a57 100644 --- a/src/hooks/Votes/vote.ts +++ b/src/hooks/Votes/vote.ts @@ -1,50 +1,28 @@ -import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; +import {useMutation, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'; -import {PostNewVote} from '@/api/vote'; -import {getVoteInfo} from '@/mocks/handlers/vote'; +import {getVoteInfo, getVoteListInfo, PostNewVote} from '@/api/vote'; -import {postVoteTitleProps} from './../../types/vote'; - -// export const useAccommodationInfoQuery = () => { -// return useQuery({ -// queryKey: [], -// queryFn: -// }); -// }; - -// export const usePostCart = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: -// onSuccess: () => { -// queryClient.invalidateQueries({ queryKey: ["fetchCarts"] }); -// setTimeout(() => setIsButtonDisabled(false), 3000); -// }, -// }); -// }; - -export const useGetVotesInfoQuery = (voteId: string) => { - return useQuery({ +//단일 보트 GET +export const useGetVotesInfo = (voteId: number) => { + return useSuspenseQuery({ queryKey: ['votes', voteId], queryFn: () => getVoteInfo(voteId), }); }; -export const usePostVoteTitle = ({spaceId, title}: postVoteTitleProps) => { +//보트 리스트 GET +export const useGetVoteListInfo = (spaceId: number) => { + return useSuspenseQuery({ + queryKey: ['votes', spaceId], + queryFn: () => getVoteListInfo(spaceId), + }); +}; + +export const usePostNewVote = () => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: () => PostNewVote({spaceId, title}), - onSuccess: () => { - queryClient.invalidateQueries({queryKey: ['votes']}); - }, + mutationFn: PostNewVote, + onSuccess: () => queryClient.invalidateQueries({queryKey: ['votes']}), }); }; - -// const { mutateAsync } = useMutation(updateData); - -// // 비동기 함수를 직접 호출하지 않음 -// const handleUpdate = async () => { -// await mutateAsync(); -// }; diff --git a/src/mocks/handlers/vote.ts b/src/mocks/handlers/vote.ts index 55cd3f58..207e3611 100644 --- a/src/mocks/handlers/vote.ts +++ b/src/mocks/handlers/vote.ts @@ -1,8 +1,4 @@ -import axios from 'axios'; import {http, HttpResponse} from 'msw'; -import {Dispatch} from 'react'; - -import {VoteInfo} from '@/types/vote'; const candidateData = [ { @@ -57,7 +53,7 @@ const candidateData = [ const voteListData = [ { - voteId: '11', + voteId: 11, title: '호텔은 여기어때', voteStatus: '진행 중', ownerProfile: { @@ -78,7 +74,7 @@ const voteListData = [ ], }, { - voteId: '22', + voteId: 22, title: '카페 고르자', voteStatus: '결정완료', ownerProfile: { @@ -101,7 +97,7 @@ const voteListData = [ ]; const voteData = { - id: '11', + id: 11, title: '호텔은 여기어때', ownerProfile: { id: '1996', @@ -110,12 +106,12 @@ const voteData = { votedMemberProfiles: [ { id: 2323, - nickName: '제니', + nickName: '로제', profile: 'https://avatars.githubusercontent.com/u/154430298?s=48&v=4', }, { id: 3333, - nickName: '리사', + nickName: '지수', profile: 'https://avatars.githubusercontent.com/u/154430298?s=48&v=4', }, ], @@ -127,7 +123,7 @@ const voteData = { //const foundObject = voteListData.find((item) => item.id === targetId); export const vote = [ - http.get('/api/votes/', () => { + http.get('/api/votes/1', () => { return HttpResponse.json(voteListData, { status: 200, }); @@ -144,34 +140,14 @@ export const vote = [ status: 200, }); }), -]; - -export async function getVoteListData(setter: Dispatch>) { - try { - const {data: res} = await axios.get('/api/votes/'); - setter(res); - } catch (error) { - console.error(error); - } -} - -export async function getVoteData(id: string, setter: Dispatch>) { - try { - const {data: res} = await axios.get(`/api/votes/${id}`); - setter(res); - } catch (error) { - console.error(error); - } -} - -//단일vote -export const getVoteInfo = async (voteId: string): Promise => { - const response = await axios.get(`/api/votes/${voteId}`); - return response.data; -}; -//여러 vote in space -export const getVoteListInfo = async (spaceId: string) => { - const response = await axios.get(`/api/votes/${spaceId}`); - return response.data.data; -}; + //vote 만들기 + http.post(`/api/votes`, () => { + return HttpResponse.json( + {data: 11, message: '투표 만들기 성공'}, + { + status: 200, + }, + ); + }), +]; diff --git a/src/mocks/jest/postNewVote.test.ts b/src/mocks/jest/postNewVote.test.ts new file mode 100644 index 00000000..cbaf39eb --- /dev/null +++ b/src/mocks/jest/postNewVote.test.ts @@ -0,0 +1,85 @@ +// import {getVoteInfo} from '../handlers/vote'; +// const candidateData = [ +// { +// id: 111, +// placeId: 11111, +// placeName: '기역호텔', +// imageURL: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', +// category: '숙박', +// location: '서울', +// voteUserId: ['Id123', 'Id234', 'Id345'], +// tagline: '예쁨~~~~~~~~~', +// amIVoted: true, +// latlng: {lat: 33.450705, lng: 126.570677}, +// }, +// { +// id: 222, +// placeId: 22222, +// placeName: '니은펜션', +// imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', +// category: '숙박', +// location: '경기', +// voteUserId: ['Id123', 'Id345'], +// tagline: '여기 개쩔드라', +// amIVoted: false, +// latlng: {lat: 33.450936, lng: 126.569477}, +// }, +// { +// id: 333, +// placeId: 33333, +// placeName: '기역호텔', +// imageURL: 'https://m.eejmall.com/web/product/big/201708/211_shop1_627935.jpg', +// category: '숙박', +// location: '서울', +// voteUserId: ['Id123', 'Id234', 'Id345'], +// tagline: '예쁨~~~~~~~~~', +// amIVoted: true, +// latlng: {lat: 33.450879, lng: 126.56994}, +// }, +// { +// id: 444, +// placeId: 44444, +// placeName: '니은펜션', +// imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', +// category: '숙박', +// location: '경기', +// voteUserId: ['Id123', 'Id345'], +// tagline: '여기 개쩔드라', +// amIVoted: false, +// latlng: {lat: 33.451393, lng: 126.570738}, +// }, +// ]; + +// // test('PostNewVote should return the correct response', async () => { +// // const response = await PostNewVote({spaceId: 11, title: 'someTitle'}); + +// // expect(response).toEqual({data: 11, message: '투표 만들기 성공'}); +// // }); + +// test('getVoteData', async () => { +// const response = getVoteInfo('11'); + +// await expect(response).toEqual({ +// id: '11', +// title: '호텔은 여기어때', +// ownerProfile: { +// id: '1996', +// profile: 'https://avatars.githubusercontent.com/u/154430298?s=48&v=4', +// }, +// votedMemberProfiles: [ +// { +// id: 2323, +// nickName: '로제', +// profile: 'https://avatars.githubusercontent.com/u/154430298?s=48&v=4', +// }, +// { +// id: 3333, +// nickName: '지수', +// profile: 'https://avatars.githubusercontent.com/u/154430298?s=48&v=4', +// }, +// ], +// voteStatus: '진행 중', +// voteUserId: ['Id123', 'Id234', 'Id345'], +// candidates: candidateData, +// }); +// }); diff --git a/src/pages/CandidatesMap/CandidatesMap.tsx b/src/pages/CandidatesMap/CandidatesMap.tsx index 23e25d3d..4360ea7e 100644 --- a/src/pages/CandidatesMap/CandidatesMap.tsx +++ b/src/pages/CandidatesMap/CandidatesMap.tsx @@ -1,26 +1,21 @@ -import {useLocation} from 'react-router-dom'; +import {useParams} from 'react-router-dom'; import styles from './CandidatesMap.module.scss'; -import {useGetVotesInfoQuery} from '@/hooks/Votes/vote'; +import {useGetVotesInfo} from '@/hooks/Votes/vote'; import CandidatesMapBody from '@/components/CandidatesMap/CandidatesMapBody/CandidatesMapBody'; import VoteHeader from '@/components/Vote/VoteHeader/VoteHeader'; const CandidatesMap = () => { - const location = useLocation(); - const voteId = location.pathname.split('/')[2]; - const {data: voteInfo} = useGetVotesInfoQuery(voteId); + const {id: voteId} = useParams(); + const {data: voteInfo} = useGetVotesInfo(Number(voteId)); return ( - <> - {voteInfo && ( -
    - - -
    - )} - +
    + + +
    ); }; diff --git a/src/pages/Vote/Vote.tsx b/src/pages/Vote/Vote.tsx index aa044c53..3428a508 100644 --- a/src/pages/Vote/Vote.tsx +++ b/src/pages/Vote/Vote.tsx @@ -5,30 +5,29 @@ import {useRecoilState, useSetRecoilState} from 'recoil'; import styles from './Vote.module.scss'; +import {useGetVotesInfo} from '@/hooks/Votes/vote'; + import BottomSlide from '@/components/BottomSlide/BottomSlide'; import VoteMeatball from '@/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball'; import VoteContent from '@/components/Vote/VoteContent/VoteContent'; import VoteContentEmpty from '@/components/Vote/VoteContent/VoteContentEmpty/VoteContentEmpty'; import VoteHeader from '@/components/Vote/VoteHeader/VoteHeader'; -import {getVoteData} from '@/mocks/handlers/vote'; import {isCandidateSelectingState} from '@/recoil/vote/alertModal'; import {isBottomSlideOpenState} from '@/recoil/vote/bottomSlide'; import {selectedCandidatesState} from '@/recoil/vote/candidateList'; -import {VoteInfo} from '@/types/vote'; - const Vote = () => { + const {id: voteId} = useParams(); + const {data: voteInfo} = useGetVotesInfo(Number(voteId)); + const [isBTOpen, setIsBTOpen] = useRecoilState(isBottomSlideOpenState); const [isCandidateSelecting, setIsCandidateSelecting] = useRecoilState(isCandidateSelectingState); const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); const [showResults, setShowResults] = useState(false); const [bottomSlideContent, setBottomSlideContent] = useState(null); - const [data, setData] = useState(); - const param = useParams().id as string; useEffect(() => { - getVoteData(param, setData); setIsCandidateSelecting(false); setShowResults(false); setSelectedCandidates(new Set()); @@ -46,31 +45,29 @@ const Vote = () => { }; return ( - <> - {data && ( -
    - - BottomSlideOpen() - } - /> +
    + + BottomSlideOpen( + , + ) + } + /> - {data.candidates ? ( - - ) : ( - - )} - {!isCandidateSelecting && data.voteStatus === '진행 중' && ( - - )} - setIsBTOpen(false)} children={bottomSlideContent} /> -
    + {voteInfo.candidates ? ( + + ) : ( + + )} + {!isCandidateSelecting && voteInfo.voteStatus === '진행 중' && ( + )} - + setIsBTOpen(false)} children={bottomSlideContent} /> +
    ); }; diff --git a/src/pages/Vote/VoteMemo/VoteMemo.tsx b/src/pages/Vote/VoteMemo/VoteMemo.tsx index 7da4a98b..375ae05a 100644 --- a/src/pages/Vote/VoteMemo/VoteMemo.tsx +++ b/src/pages/Vote/VoteMemo/VoteMemo.tsx @@ -1,51 +1,42 @@ import {Button} from '@chakra-ui/react'; -import {useEffect, useState} from 'react'; import {useNavigate, useParams} from 'react-router-dom'; import {useRecoilState, useRecoilValue} from 'recoil'; import styles from './VoteMemo.module.scss'; +import {useGetVotesInfo} from '@/hooks/Votes/vote'; + import BottomSlide from '@/components/BottomSlide/BottomSlide'; import AddCandidate from '@/components/Vote/VoteBottomSlideContent/AddCandidate/AddCandidate'; import VoteHeader from '@/components/Vote/VoteHeader/VoteHeader'; import MemoContent from '@/components/VoteMemo/MemoContent'; -import {getVoteData} from '@/mocks/handlers/vote'; import {isBottomSlideOpenState} from '@/recoil/vote/bottomSlide'; import {selectedCandidatesState} from '@/recoil/vote/candidateList'; -import {VoteInfo} from '@/types/vote'; - const VoteMemo = () => { - const param = useParams().id as string; + const {id: voteId} = useParams(); + const {data: voteInfo} = useGetVotesInfo(Number(voteId)); const navigate = useNavigate(); - const [data, setData] = useState(); + const [isBTOpen, setIsBTOpen] = useRecoilState(isBottomSlideOpenState); const selectedCandidates = useRecoilValue(selectedCandidatesState); - useEffect(() => { - getVoteData(param, setData); - }, []); - return ( - <> - {data && ( -
    - setIsBTOpen(true)} /> - - - - - setIsBTOpen(false)} children={} /> -
    - )} - +
    + setIsBTOpen(true)} /> + + + + + setIsBTOpen(false)} children={} /> +
    ); }; diff --git a/src/types/vote.ts b/src/types/vote.ts index 9c0a1bfe..d2f49372 100644 --- a/src/types/vote.ts +++ b/src/types/vote.ts @@ -1,27 +1,6 @@ import {Dispatch, ReactNode} from 'react'; import {SwiperRef} from 'swiper/react'; -export interface CandidateData { - name: string; - imageURL: string; - category: string; - location: string; - voteUserId: string[]; - voteCounts: number; - memo: string; - id: number; -} -[]; - -export interface VoteListData { - title: string; - profile: string; - state: string; - voteUserId: string[]; - candidates: CandidateData[]; - id: string; -} - export interface Latlng { lat: number; lng: number; @@ -51,7 +30,7 @@ interface VotedMemberProfiles { []; export interface VoteListInfo { - voteId: string; + voteId: number; title: string; voteStatus: string; ownerProfile: { @@ -63,7 +42,7 @@ export interface VoteListInfo { } export interface VoteInfo { - id: string; + id: number; title: string; ownerProfile: { id: number; @@ -134,7 +113,7 @@ export interface postVoteTitleProps { //get아니고 후보메모post export interface postTaglineProps { - voteId: string; + voteId: number; placeId: number; tagline: string; } From 716fb276714c9c12b9e811d278bc18832292d41a Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sat, 20 Jan 2024 19:39:18 +0900 Subject: [PATCH 18/50] Feat: edit postVotes react-query --- src/api/vote.ts | 4 ++-- .../CreatVoteTitleModal/CreatVoteTitleModal.tsx | 7 +++---- src/components/VoteTabPanel/VoteTabPanel.tsx | 2 +- src/hooks/Votes/vote.ts | 4 +++- src/mocks/handlers/vote.ts | 14 +++++++------- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/api/vote.ts b/src/api/vote.ts index a6be7453..3fd803f5 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -19,8 +19,8 @@ export const getVoteListInfo = async (spaceId: number): Promise //vote 추가 export const PostNewVote = async ({spaceId, title}: postVoteTitleProps) => { try { - const response = await axios.post('/api/votes', {params: {spaceId, title}}); - console.log('response', response); + const response = await axios.post('/api/votes', {spaceId, title}); + console.log('axios 성공', response); return response; } catch (error) { console.error(error); diff --git a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx index bcdcb031..9f596c78 100644 --- a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx +++ b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx @@ -22,7 +22,7 @@ import {usePostNewVote} from '@/hooks/Votes/vote'; import VoteIcon from '@/assets/voteIcons/ic_vote.svg?react'; -const CreatVoteTitleModal = () => { +const CreatVoteTitleModal = (isEditMode: boolean) => { const {isOpen, onOpen, onClose} = useDisclosure(); const location = useLocation(); const spaceId = Number(location.pathname.split('/')[2]); @@ -36,10 +36,9 @@ const CreatVoteTitleModal = () => { }; const handleCreateVote = (inputValue: string) => { - const res = postNewVoteMutation.mutate({spaceId: 1, title: inputValue}); + const res = postNewVoteMutation.mutate({spaceId, title: inputValue}); newClose(); - console.log('res', res); // navigate(`/vote/${data.id}`) }; @@ -93,7 +92,7 @@ const CreatVoteTitleModal = () => {
    ); }; diff --git a/src/hooks/Votes/vote.ts b/src/hooks/Votes/vote.ts index fe712a57..05482162 100644 --- a/src/hooks/Votes/vote.ts +++ b/src/hooks/Votes/vote.ts @@ -23,6 +23,8 @@ export const usePostNewVote = () => { return useMutation({ mutationFn: PostNewVote, - onSuccess: () => queryClient.invalidateQueries({queryKey: ['votes']}), + onSuccess: (data) => { + console.log('투표 만들기 성공:', data), queryClient.invalidateQueries({queryKey: ['votes']}); + }, }); }; diff --git a/src/mocks/handlers/vote.ts b/src/mocks/handlers/vote.ts index 207e3611..fb6550c8 100644 --- a/src/mocks/handlers/vote.ts +++ b/src/mocks/handlers/vote.ts @@ -122,6 +122,11 @@ const voteData = { //const foundObject = voteListData.find((item) => item.id === targetId); +const postVotesRes = { + message: '투표 만들기 성공', + data: 33, +}; + export const vote = [ http.get('/api/votes/1', () => { return HttpResponse.json(voteListData, { @@ -142,12 +147,7 @@ export const vote = [ }), //vote 만들기 - http.post(`/api/votes`, () => { - return HttpResponse.json( - {data: 11, message: '투표 만들기 성공'}, - { - status: 200, - }, - ); + http.post('/api/votes', () => { + return HttpResponse.json(postVotesRes, {status: 200}); }), ]; From 065a5ed2380352d3c17dbc31fcc6a22f62514519 Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sat, 20 Jan 2024 19:56:05 +0900 Subject: [PATCH 19/50] Design: separate CreateVoteModal into modal and button --- .../CreatVoteTitleModal.module.scss | 6 - .../CreatVoteTitleModal.tsx | 110 ------------------ .../Vote/CreateVoteModal/CreateVoteModal.tsx | 100 ++++++++++++++++ .../CreateVoteModal/CreateVoteModalButton.tsx | 17 +++ src/components/VoteTabPanel/VoteTabPanel.tsx | 6 +- src/recoil/vote/createVoteTitleModal.ts | 6 + 6 files changed, 127 insertions(+), 118 deletions(-) delete mode 100644 src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.module.scss delete mode 100644 src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx create mode 100644 src/components/Vote/CreateVoteModal/CreateVoteModal.tsx create mode 100644 src/components/Vote/CreateVoteModal/CreateVoteModalButton.tsx create mode 100644 src/recoil/vote/createVoteTitleModal.ts diff --git a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.module.scss b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.module.scss deleted file mode 100644 index 8227371e..00000000 --- a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -@use "@/sass" as *; - -.container { - display: flex; - margin-top: 48px; -} diff --git a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx b/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx deleted file mode 100644 index 9f596c78..00000000 --- a/src/components/Vote/CreatVoteTitleModal/CreatVoteTitleModal.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { - Button, - FormControl, - FormLabel, - Icon, - Input, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - ModalOverlay, - useDisclosure, -} from '@chakra-ui/react'; -import {useState} from 'react'; -import {useLocation} from 'react-router-dom'; - -import styles from './CreatVoteTitleModal.module.scss'; - -import {usePostNewVote} from '@/hooks/Votes/vote'; - -import VoteIcon from '@/assets/voteIcons/ic_vote.svg?react'; - -const CreatVoteTitleModal = (isEditMode: boolean) => { - const {isOpen, onOpen, onClose} = useDisclosure(); - const location = useLocation(); - const spaceId = Number(location.pathname.split('/')[2]); - const [inputValue, setInputValue] = useState(''); - const postNewVoteMutation = usePostNewVote(); - // const navigate = useNavigate(); - - const newClose = () => { - setInputValue(''); - onClose(); - }; - - const handleCreateVote = (inputValue: string) => { - const res = postNewVoteMutation.mutate({spaceId, title: inputValue}); - newClose(); - - // navigate(`/vote/${data.id}`) - }; - - return ( -
    - - - - - - - 투표 제목을 정해주세요 - - - - - - setInputValue(e.target.value)} - maxLength={15} - borderColor='neutral.800' - focusBorderColor='primary.300' - variant='flushed' - placeholder=' 숙소 정하자, 카페 정하자' - fontSize='subTitle' - /> - - {inputValue.length}/15자 - - - - - - - - - -
    - ); -}; - -export default CreatVoteTitleModal; diff --git a/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx b/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx new file mode 100644 index 00000000..10b924af --- /dev/null +++ b/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx @@ -0,0 +1,100 @@ +import { + Button, + FormControl, + FormLabel, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react'; +import {useState} from 'react'; +import {useLocation} from 'react-router-dom'; +import {useRecoilState} from 'recoil'; + +import {usePostNewVote} from '@/hooks/Votes/vote'; + +import {isCreateModalOpenState} from '@/recoil/vote/createVoteTitleModal'; + +const CreateVoteModal = ({isEditMode}: {isEditMode: boolean}) => { + const [isCreateModalOpen, setIsCreateModalOpen] = useRecoilState(isCreateModalOpenState); + const location = useLocation(); + const spaceId = Number(location.pathname.split('/')[2]); + const [inputValue, setInputValue] = useState(''); + const postNewVoteMutation = usePostNewVote(); + // const navigate = useNavigate(); + + const newClose = () => { + setInputValue(''); + setIsCreateModalOpen(true); + }; + + const handleCreateVote = (inputValue: string) => { + const res = postNewVoteMutation.mutate({spaceId, title: inputValue}); + newClose(); + + // navigate(`/vote/${data.id}`) + }; + + return ( + setIsCreateModalOpen(false)}> + + + + 투표 제목을 정해주세요 + + + + + + setInputValue(e.target.value)} + maxLength={15} + borderColor='neutral.800' + focusBorderColor='primary.300' + variant='flushed' + placeholder=' 숙소 정하자, 카페 정하자' + fontSize='subTitle' + /> + + {inputValue.length}/15자 + + + + + + + + + + ); +}; + +export default CreateVoteModal; diff --git a/src/components/Vote/CreateVoteModal/CreateVoteModalButton.tsx b/src/components/Vote/CreateVoteModal/CreateVoteModalButton.tsx new file mode 100644 index 00000000..08e79980 --- /dev/null +++ b/src/components/Vote/CreateVoteModal/CreateVoteModalButton.tsx @@ -0,0 +1,17 @@ +import {Button, Icon} from '@chakra-ui/react'; +import {useSetRecoilState} from 'recoil'; + +import VoteIcon from '@/assets/voteIcons/ic_vote.svg?react'; +import {isCreateModalOpenState} from '@/recoil/vote/createVoteTitleModal'; + +const CreateVoteModalButton = () => { + const setIsCreateModalOpen = useSetRecoilState(isCreateModalOpenState); + return ( + + ); +}; + +export default CreateVoteModalButton; diff --git a/src/components/VoteTabPanel/VoteTabPanel.tsx b/src/components/VoteTabPanel/VoteTabPanel.tsx index d8c88f53..d610f540 100644 --- a/src/components/VoteTabPanel/VoteTabPanel.tsx +++ b/src/components/VoteTabPanel/VoteTabPanel.tsx @@ -7,7 +7,8 @@ import {useGetVoteListInfo} from '@/hooks/Votes/vote'; import TabsVoteCard from './TabsVoteCard/TabsVoteCard'; import VoteTabPanelEmpty from './VoteTabPanelEmpty/VoteTabPanelEmpty'; -import CreatVoteTitleModal from '../Vote/CreatVoteTitleModal/CreatVoteTitleModal'; +import CreateVoteModal from '../Vote/CreateVoteModal/CreateVoteModal'; +import CreateVoteModalButton from '../Vote/CreateVoteModal/CreateVoteModalButton'; const VoteTabPanel = () => { const {id: voteId} = useParams(); @@ -42,7 +43,8 @@ const VoteTabPanel = () => { - + +
    ); }; diff --git a/src/recoil/vote/createVoteTitleModal.ts b/src/recoil/vote/createVoteTitleModal.ts new file mode 100644 index 00000000..56fcc18e --- /dev/null +++ b/src/recoil/vote/createVoteTitleModal.ts @@ -0,0 +1,6 @@ +import {atom} from 'recoil'; + +export const isCreateModalOpenState = atom({ + key: 'isCreateModalOpenState', + default: false, +}); From 5565dfb26063305ddc24f58e2aa84ace2f817e59 Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sat, 20 Jan 2024 21:45:10 +0900 Subject: [PATCH 20/50] Feat: add EditVoteTitle and add Suspense --- src/App.tsx | 17 +++-- src/api/vote.ts | 16 ++++- .../Vote/CreateVoteModal/CreateVoteModal.tsx | 37 ++++++++--- .../VoteMeatball/VoteMeatball.tsx | 65 +++++++++---------- src/hooks/Votes/vote.ts | 13 +++- src/mocks/handlers/vote.ts | 17 ++++- src/pages/Vote/Vote.tsx | 8 ++- src/types/vote.ts | 19 ++++-- 8 files changed, 124 insertions(+), 68 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 52a1b259..d6ee9518 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import {QueryClient, QueryClientProvider} from '@tanstack/react-query'; +import {Suspense} from 'react'; import {CookiesProvider} from 'react-cookie'; import {BrowserRouter} from 'react-router-dom'; @@ -9,13 +10,15 @@ import MainRouter from './routes/MainRouter/MainRouter'; const queryClient = new QueryClient(); function App() { return ( - - - - - - - + + + + + + + + + ); } diff --git a/src/api/vote.ts b/src/api/vote.ts index 3fd803f5..0e1f76a9 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -import {postTaglineProps, postVoteTitleProps, VoteInfo, VoteListInfo} from '@/types/vote'; +import {EditVoteTitleProps, PostTaglineProps, PostVoteTitleProps, VoteInfo, VoteListInfo} from '@/types/vote'; //단일vote export const getVoteInfo = async (voteId: number): Promise => { @@ -17,7 +17,7 @@ export const getVoteListInfo = async (spaceId: number): Promise //getVotesResults //vote 추가 -export const PostNewVote = async ({spaceId, title}: postVoteTitleProps) => { +export const PostNewVote = async ({spaceId, title}: PostVoteTitleProps) => { try { const response = await axios.post('/api/votes', {spaceId, title}); console.log('axios 성공', response); @@ -28,7 +28,17 @@ export const PostNewVote = async ({spaceId, title}: postVoteTitleProps) => { }; //후보 메모 후 추가 -export const postTagline = async ({voteId, placeId, tagline}: postTaglineProps) => { +export const postTagline = async ({voteId, placeId, tagline}: PostTaglineProps) => { const response = await axios.post(`/api/votes/${voteId}`, {params: {placeId, tagline}}); return response.data; }; + +//voteTitle 수정 //api미정 +export const editVoteTitle = async ({title, voteId}: EditVoteTitleProps) => { + try { + const response = await axios.put(`/api/votes/${voteId}`, {params: title}); + return response.data; + } catch (error) { + console.error(error); + } +}; diff --git a/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx b/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx index 10b924af..f31e36fd 100644 --- a/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx +++ b/src/components/Vote/CreateVoteModal/CreateVoteModal.tsx @@ -11,36 +11,50 @@ import { ModalHeader, ModalOverlay, } from '@chakra-ui/react'; -import {useState} from 'react'; +import {useEffect, useState} from 'react'; import {useLocation} from 'react-router-dom'; import {useRecoilState} from 'recoil'; -import {usePostNewVote} from '@/hooks/Votes/vote'; +import {useEditVoteTitle, usePostNewVote} from '@/hooks/Votes/vote'; import {isCreateModalOpenState} from '@/recoil/vote/createVoteTitleModal'; -const CreateVoteModal = ({isEditMode}: {isEditMode: boolean}) => { +import {CreateVoteModalProps} from '@/types/vote'; + +const CreateVoteModal = ({isEditMode, existingTitle}: CreateVoteModalProps) => { const [isCreateModalOpen, setIsCreateModalOpen] = useRecoilState(isCreateModalOpenState); const location = useLocation(); - const spaceId = Number(location.pathname.split('/')[2]); + const path = Number(location.pathname.split('/')[2]); const [inputValue, setInputValue] = useState(''); const postNewVoteMutation = usePostNewVote(); + const editTitleMutation = useEditVoteTitle(); // const navigate = useNavigate(); + useEffect(() => { + if (existingTitle) { + setInputValue(existingTitle); + } + }, [existingTitle]); + const newClose = () => { - setInputValue(''); - setIsCreateModalOpen(true); + existingTitle ? setInputValue(existingTitle) : setInputValue(''); + setIsCreateModalOpen(false); }; const handleCreateVote = (inputValue: string) => { - const res = postNewVoteMutation.mutate({spaceId, title: inputValue}); + const res = postNewVoteMutation.mutate({spaceId: path, title: inputValue}); newClose(); - // navigate(`/vote/${data.id}`) }; + const handleEditVoteTitle = (inputValue: string) => { + setIsCreateModalOpen(false); + console.log('inputValue', inputValue); + const res = editTitleMutation.mutate({voteId: path, title: inputValue}); + }; + return ( - setIsCreateModalOpen(false)}> + { borderColor='neutral.800' focusBorderColor='primary.300' variant='flushed' + value={inputValue} placeholder=' 숙소 정하자, 카페 정하자' fontSize='subTitle' /> @@ -83,7 +98,9 @@ const CreateVoteModal = ({isEditMode}: {isEditMode: boolean}) => { @@ -72,7 +68,7 @@ const VoteMeatball = ({ state, isZeroCandidates }: VoteMeatballProps) => { )} - @@ -82,16 +78,13 @@ const VoteMeatball = ({ state, isZeroCandidates }: VoteMeatballProps) => {

    후보 삭제

    - +
    ); }; diff --git a/src/hooks/Votes/vote.ts b/src/hooks/Votes/vote.ts index 05482162..387ba9fc 100644 --- a/src/hooks/Votes/vote.ts +++ b/src/hooks/Votes/vote.ts @@ -1,6 +1,6 @@ import {useMutation, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'; -import {getVoteInfo, getVoteListInfo, PostNewVote} from '@/api/vote'; +import {editVoteTitle, getVoteInfo, getVoteListInfo, PostNewVote} from '@/api/vote'; //단일 보트 GET export const useGetVotesInfo = (voteId: number) => { @@ -28,3 +28,14 @@ export const usePostNewVote = () => { }, }); }; + +export const useEditVoteTitle = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: editVoteTitle, + onSuccess: () => { + queryClient.invalidateQueries({queryKey: ['votesTitle']}); + }, + }); +}; diff --git a/src/mocks/handlers/vote.ts b/src/mocks/handlers/vote.ts index fb6550c8..2f6a5595 100644 --- a/src/mocks/handlers/vote.ts +++ b/src/mocks/handlers/vote.ts @@ -128,26 +128,39 @@ const postVotesRes = { }; export const vote = [ + //voteList http.get('/api/votes/1', () => { return HttpResponse.json(voteListData, { status: 200, }); }), + //vote id:11 http.get(`/api/votes/11`, () => { return HttpResponse.json(voteData, { status: 200, }); }), - + //vote id:22 http.get(`/api/votes/22`, () => { return HttpResponse.json(voteData, { status: 200, }); }), + //vote 결과보기 GET /votes/{voteId}/result - //vote 만들기 + //vote 만들기 POST http.post('/api/votes', () => { return HttpResponse.json(postVotesRes, {status: 200}); }), + + ///votes/{voteId}/candidates candidate메모와 함께 추가 POST + + //votes/{voteId}/candidates/{candidatesId} 별 투표하기 POST + + //vote 삭제 DELETE /votes/{voteId} ]; + +//vote 제목 수정 PUT + +//candidate 삭제 DELETE diff --git a/src/pages/Vote/Vote.tsx b/src/pages/Vote/Vote.tsx index 3428a508..3f2a4d2b 100644 --- a/src/pages/Vote/Vote.tsx +++ b/src/pages/Vote/Vote.tsx @@ -48,10 +48,14 @@ const Vote = () => {
    BottomSlideOpen( - , + , ) } /> diff --git a/src/types/vote.ts b/src/types/vote.ts index d2f49372..5f106584 100644 --- a/src/types/vote.ts +++ b/src/types/vote.ts @@ -82,6 +82,7 @@ export interface CandidateCardProps { export interface VoteMeatballProps { state: string; + title: string; isZeroCandidates: boolean; } @@ -101,18 +102,13 @@ export interface CandidateListProps { isCandidateSelecting: boolean; } -export interface postVoteTitleProps { - spaceId: number; - title: string; -} - -export interface postVoteTitleProps { +export interface PostVoteTitleProps { spaceId: number; title: string; } //get아니고 후보메모post -export interface postTaglineProps { +export interface PostTaglineProps { voteId: number; placeId: number; tagline: string; @@ -124,3 +120,12 @@ export interface CandidatesSlideProps { setCenterMarker: Dispatch>; swiperRef: React.RefObject; } + +export interface CreateVoteModalProps { + isEditMode: boolean; + existingTitle?: string; +} +export interface EditVoteTitleProps { + title: string; + voteId: number; +} From ca37d17c096e09fd5cd713830294d07201c6f838 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Sun, 21 Jan 2024 00:47:29 +0900 Subject: [PATCH 21/50] Feat: add tooltip --- src/assets/icons/error-warning-line.svg | 10 +++ src/assets/icons/tooltipFrame.svg | 3 + src/pages/User/User.module.scss | 107 ++++++++++++++++++------ src/pages/User/User.tsx | 30 ++++++- 4 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 src/assets/icons/error-warning-line.svg create mode 100644 src/assets/icons/tooltipFrame.svg diff --git a/src/assets/icons/error-warning-line.svg b/src/assets/icons/error-warning-line.svg new file mode 100644 index 00000000..05fe8515 --- /dev/null +++ b/src/assets/icons/error-warning-line.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/tooltipFrame.svg b/src/assets/icons/tooltipFrame.svg new file mode 100644 index 00000000..81599275 --- /dev/null +++ b/src/assets/icons/tooltipFrame.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/User/User.module.scss b/src/pages/User/User.module.scss index fa979cc2..2a7cd2e6 100644 --- a/src/pages/User/User.module.scss +++ b/src/pages/User/User.module.scss @@ -81,7 +81,7 @@ } .infoDetail { - li { + & > li { display: flex; justify-content: space-between; align-items: center; @@ -103,34 +103,82 @@ } &__alert { - position: relative; - width: 5.6rem; - height: 3.2rem; - border-radius: 32px; + &__left { + display: flex; + align-items: center; + gap: 4px; + + &__tooltip { + position: relative; + + .tooltipList { + display: none; + position: absolute; + left: -38px; + width: 20rem; + padding: 21px 8px 8px 16px; + margin-top: 2px; + border-radius: 8px; + + background-image: url("@/assets/icons/tooltipFrame.svg"); + background-repeat: no-repeat; + background-size: auto; + + list-style-type: disc; + list-style-position: inside; + + @include typography(captionSmall); + line-height: 1.8rem; + color: $neutral0; + + @include animate(showTooltip, 0.2s, ease-in, forwards); + + & > li > span { + margin-left: -5px; + } + } + + & > button { + display: block; + + &:focus ~ .tooltipList, + &:hover ~ .tooltipList { + display: block; + } + } + } + } - .alertState { - position: absolute; - top: 0; - width: 3.2rem; + &__button { + position: relative; + width: 5.6rem; height: 3.2rem; + border-radius: 32px; - background-color: $neutral0; - border-radius: 50%; - transition: all 0.2s ease-in; - } - - &.on { - background-color: $success300; .alertState { - left: calc(100% - 3.2rem); - border: 2px solid $success300; + position: absolute; + top: 0; + width: 3.2rem; + height: 3.2rem; + + background-color: $neutral0; + border-radius: 50%; + transition: all 0.2s ease-in; } - } - &.off { - background-color: $neutral200; - .alertState { - left: 0; - border: 2px solid $neutral200; + + &.on { + background-color: $success300; + .alertState { + left: calc(100% - 3.2rem); + border: 2px solid $success300; + } + } + &.off { + background-color: $neutral200; + .alertState { + left: 0; + border: 2px solid $neutral200; + } } } } @@ -140,3 +188,14 @@ } } } + +@include keyframes(showTooltip) { + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 37072f76..40380ae5 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -4,6 +4,7 @@ import { useNavigate } from "react-router-dom"; import styles from "./User.module.scss"; +import AlertIcon from "@/assets/icons/error-warning-line.svg?react"; import MapPin from "@/assets/icons/mapPin.svg?react"; import Pencil from "@/assets/icons/pencil.svg?react"; import Star from "@/assets/icons/star.svg?react"; @@ -76,11 +77,34 @@ function User() { -
  • -
    알림
    +
  • +
    + 알림 +
    + + +
      +
    • + + 구글 크롬(Chrome) 브라우저에 최적화 되어 있어 크롬 브라우저 + 사용을 권장합니다. + +
    • +
    • + + 브라우저의 알림 설정을 켜주셔야 서비스 알림을 받을 수 + 있습니다. + +
    • +
    +
    +
    + - diff --git a/src/hooks/Votes/vote.ts b/src/hooks/Votes/vote.ts index 387ba9fc..adf08bd6 100644 --- a/src/hooks/Votes/vote.ts +++ b/src/hooks/Votes/vote.ts @@ -1,11 +1,13 @@ import {useMutation, useQueryClient, useSuspenseQuery} from '@tanstack/react-query'; -import {editVoteTitle, getVoteInfo, getVoteListInfo, PostNewVote} from '@/api/vote'; +import {deleteVote, editVoteTitle, getVoteInfo, getVoteListInfo, PostNewVote} from '@/api/vote'; + +/* ----------------------------------- Q U E R Y ---------------------------------- */ //단일 보트 GET export const useGetVotesInfo = (voteId: number) => { return useSuspenseQuery({ - queryKey: ['votes', voteId], + queryKey: ['vote', voteId], queryFn: () => getVoteInfo(voteId), }); }; @@ -18,24 +20,67 @@ export const useGetVoteListInfo = (spaceId: number) => { }); }; -export const usePostNewVote = () => { - const queryClient = useQueryClient(); +/* ----------------------------------- M U T A T I O N ---------------------------------- */ +//커스텀 mutation +export const useCustomMutation = ( + mutationFn: (variables: TVariables) => Promise, + queryKey: string[], +) => { + const queryClient = useQueryClient(); return useMutation({ - mutationFn: PostNewVote, - onSuccess: (data) => { - console.log('투표 만들기 성공:', data), queryClient.invalidateQueries({queryKey: ['votes']}); + mutationFn, + onSuccess: () => queryClient.invalidateQueries({queryKey}), + onError: (error: TError) => { + console.error('Mutation error:', error); }, }); }; +//vote 추가 POST +export const usePostNewVote = () => { + return useCustomMutation(PostNewVote, ['votes']); +}; + +//voteTitle 수정 PUT export const useEditVoteTitle = () => { - const queryClient = useQueryClient(); + return useCustomMutation(editVoteTitle, ['vote']); +}; - return useMutation({ - mutationFn: editVoteTitle, - onSuccess: () => { - queryClient.invalidateQueries({queryKey: ['votesTitle']}); - }, - }); +//voteTitle 수정 PUT +export const useDeleteVote = () => { + return useCustomMutation(deleteVote, ['votes']); }; + +//커스텀 mutation 사용 전 + +//vote 추가 POST +// export const usePostNewVote = () => { +// const queryClient = useQueryClient(); +// return useMutation({ +// mutationFn: PostNewVote, +// onSuccess: () => queryClient.invalidateQueries({queryKey: ['votes']}); +// }); +// }; + +//voteTitle 수정 PUT +// export const useEditVoteTitle = () => { +// const queryClient = useQueryClient(); +// return useMutation({ +// mutationFn: editVoteTitle, +// onSuccess: () => { +// queryClient.invalidateQueries({queryKey: ['vote']}); +// }, +// }); +// }; + +//voteTitle 수정 PUT +// export const useDeleteVote = () => { +// const queryClient = useQueryClient(); +// return useMutation({ +// mutationFn: deleteVote, +// onSuccess: () => { +// queryClient.invalidateQueries({queryKey: ['votes']}); +// }, +// }); +// }; diff --git a/src/mocks/handlers/vote.ts b/src/mocks/handlers/vote.ts index 2f6a5595..4a43b4f0 100644 --- a/src/mocks/handlers/vote.ts +++ b/src/mocks/handlers/vote.ts @@ -130,22 +130,16 @@ const postVotesRes = { export const vote = [ //voteList http.get('/api/votes/1', () => { - return HttpResponse.json(voteListData, { - status: 200, - }); + return HttpResponse.json(voteListData, {status: 200}); }), //vote id:11 http.get(`/api/votes/11`, () => { - return HttpResponse.json(voteData, { - status: 200, - }); + return HttpResponse.json(voteData, {status: 200}); }), //vote id:22 http.get(`/api/votes/22`, () => { - return HttpResponse.json(voteData, { - status: 200, - }); + return HttpResponse.json(voteData, {status: 200}); }), //vote 결과보기 GET /votes/{voteId}/result @@ -159,8 +153,10 @@ export const vote = [ //votes/{voteId}/candidates/{candidatesId} 별 투표하기 POST //vote 삭제 DELETE /votes/{voteId} + http.delete('/api/votes/11', () => { + return HttpResponse.json({data: '투표 삭제 성공'}, {status: 200}); + }), ]; //vote 제목 수정 PUT - //candidate 삭제 DELETE From 951ea9bf97a3de2ad435eee4b443d4ab54c6f9b5 Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sun, 21 Jan 2024 03:35:53 +0900 Subject: [PATCH 23/50] Feat: add delete candidate function --- src/api/vote.ts | 41 +++++++++++----- .../DeleteCandidatesButton.tsx | 48 +++++++++---------- src/hooks/Votes/vote.ts | 11 +++-- src/mocks/handlers/vote.ts | 5 ++ src/types/vote.ts | 12 +++-- 5 files changed, 75 insertions(+), 42 deletions(-) diff --git a/src/api/vote.ts b/src/api/vote.ts index 81be9934..71091d28 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -1,15 +1,23 @@ import axios from 'axios'; -import {EditVoteTitleProps, PostTaglineProps, PostVoteTitleProps, VoteInfo, VoteListInfo} from '@/types/vote'; +import { + DeleteCandidatesProps, + EditVoteTitleProps, + PostNewCandidateProps, + PostVoteTitleProps, + VoteInfo, + VoteListInfo, +} from '@/types/vote'; /* ----------------------------------- G E T ---------------------------------- */ -//단일vote + +//단일 보트 export const getVoteInfo = async (voteId: number): Promise => { const response = await axios.get(`/api/votes/${voteId}`); return response.data; }; -//voteList in space +//보트 리스트 export const getVoteListInfo = async (spaceId: number): Promise => { const response = await axios.get(`/api/votes/${spaceId}`); return response.data; @@ -18,6 +26,7 @@ export const getVoteListInfo = async (spaceId: number): Promise //getVotesResults /* ----------------------------------- P O S T ---------------------------------- */ + //vote 추가 export const PostNewVote = async ({spaceId, title}: PostVoteTitleProps) => { try { @@ -30,14 +39,14 @@ export const PostNewVote = async ({spaceId, title}: PostVoteTitleProps) => { }; //후보 메모 후 추가 -export const postTagline = async ({voteId, placeId, tagline}: PostTaglineProps) => { - const response = await axios.post(`/api/votes/${voteId}`, {params: {placeId, tagline}}); +export const postNewCandidate = async ({voteId, candidates}: PostNewCandidateProps) => { + const response = await axios.post(`/api/votes/${voteId}`, {params: candidates}); return response.data; }; /* ----------------------------------- P U T ---------------------------------- */ -//voteTitle 수정 PUT //api미정 +//voteTitle 수정 PUT - api미정 export const editVoteTitle = async ({title, voteId}: EditVoteTitleProps) => { try { const response = await axios.put(`/api/votes/${voteId}`, {params: title}); @@ -48,7 +57,8 @@ export const editVoteTitle = async ({title, voteId}: EditVoteTitleProps) => { }; /* ----------------------------------- D E L E T E ---------------------------------- */ -//votes/{voteId} vote 삭제 DELETE + +//votes/{voteId} vote 삭제 export const deleteVote = async (voteId: number) => { try { const response = await axios.delete(`/api/votes/${voteId}`); @@ -58,10 +68,19 @@ export const deleteVote = async (voteId: number) => { } }; -///votes/{voteId}/candidates candidate메모와 함께 추가 POST +//msw 돌려야함 +//candidate 삭제- api 미정 / candidateId:number[] 여러아이디 배열에 담음 +export const deleteCandidates = async ({voteId, candidateId}: DeleteCandidatesProps) => { + try { + const response = await axios.delete(`/api/votes/${voteId}/candidates/${candidateId}`); + return response.data; + } catch (error) { + console.error(error); + } +}; -//votes/{voteId}/candidates/{candidatesId} 별 투표하기 POST -> 투표 삭제? +///votes/{voteId}/candidates candidate메모와 함께 추가 POST -//candidate 삭제 DELETE +//votes/{voteId}/candidates/{candidatesId} 별 투표하기 POST -> + DELETE 투표 삭제? -//PUT 투표 상태 변경 -> ? +//PUT 투표 상태 변경(재진행) -> ? diff --git a/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx b/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx index 09debb31..94faa215 100644 --- a/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx +++ b/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx @@ -1,50 +1,48 @@ -import { HiOutlineTrash } from "react-icons/hi"; -import { IoMdClose } from "react-icons/io"; -import { useRecoilState, useSetRecoilState } from "recoil"; +import {HiOutlineTrash} from 'react-icons/hi'; +import {IoMdClose} from 'react-icons/io'; +import {useParams} from 'react-router-dom'; +import {useRecoilState, useSetRecoilState} from 'recoil'; -import styles from "./DeleteCandidatesButton.module.scss"; +import styles from './DeleteCandidatesButton.module.scss'; -import AlertModal from "@/components/AlertModal/AlertModal"; +import {useDeleteCandidates} from '@/hooks/Votes/vote'; -// import useSetAlertModalContent from "@/hooks/useSetAlertModalContent"; -import { - isCandidateSelectingState, - isModalOpenState, - modalContentState, -} from "@/recoil/vote/alertModal"; -import { selectedCandidatesState } from "@/recoil/vote/candidateList"; +import AlertModal from '@/components/AlertModal/AlertModal'; -import { deleteCandidateContent } from "../VoteBottomSlideContent/VoteMeatball/modalContent"; +import {isCandidateSelectingState, isModalOpenState, modalContentState} from '@/recoil/vote/alertModal'; +import {selectedCandidatesState} from '@/recoil/vote/candidateList'; + +import {deleteCandidateContent} from '../VoteBottomSlideContent/VoteMeatball/modalContent'; const DeleteCandidatesButton = () => { - const [selectedCandidates, setSelectedCandidates] = useRecoilState( - selectedCandidatesState, - ); + const {id: voteId} = useParams(); + const [selectedCandidates, setSelectedCandidates] = useRecoilState(selectedCandidatesState); + const [modalContent, setModalContent] = useRecoilState(modalContentState); const setIsCandidateSelecting = useSetRecoilState(isCandidateSelectingState); const setIsModalOpen = useSetRecoilState(isModalOpenState); - const [modalContent, setModalContent] = useRecoilState(modalContentState); + + const deleteCandidateMutation = useDeleteCandidates(); + + const deleteCandidate = () => { + deleteCandidateMutation.mutate({voteId: Number(voteId), candidateId: [...selectedCandidates]}); + }; const showDeleteCandidateModal = () => { - //삭제api - console.log("삭제하기모달오픈"); setIsModalOpen(true); setModalContent({ ...deleteCandidateContent, - onClickAction: () => console.log("삭제됨~~~", selectedCandidates), + onClickAction: deleteCandidate, }); }; - const cancelSelectingCandidate = () => { + const cancelSelecting = () => { setIsCandidateSelecting(false); setSelectedCandidates(new Set()); }; return (
    - diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index 35f5dab9..ae472742 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -14,6 +14,7 @@ import RegionSearch from "@/pages/RegionSearch/RegionSearch"; import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; import Trip from "@/pages/Trip/Trip"; import ModifyProfile from "@/pages/User/ModifyProfile/ModifyProfile"; +import MySpace from "@/pages/User/MySpace/MySpace"; import User from "@/pages/User/User"; import UserPrivacy from "@/pages/User/UserPrivacy/UserPrivacy"; import Vote from "@/pages/Vote/Vote"; @@ -29,6 +30,7 @@ function MainRouter() { } /> } /> } /> + } /> } /> } /> From 460a66c18fa16a64a675a541150ce518ff0209fb Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sun, 21 Jan 2024 05:41:36 +0900 Subject: [PATCH 25/50] Feat: markup selectButton in addCandidate --- .../SelectButton/SelectButton.module.scss | 20 +++++++++++++++++++ .../SelectButton/SelectButton.tsx | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.module.scss create mode 100644 src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.module.scss b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.module.scss new file mode 100644 index 00000000..cd730d91 --- /dev/null +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.module.scss @@ -0,0 +1,20 @@ +@use '@/sass' as *; + +.container { + @include typography(captionSmall); + width: 5.3rem; + height: 2.8rem; + border-radius: 16px; + + background-color: $neutral100; + + padding: 4px auto; + + color: $neutral800; + text-align: center; +} + +.clicked { + background-color: $primary300; + color: $neutral0; +} diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx new file mode 100644 index 00000000..3fe66de2 --- /dev/null +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx @@ -0,0 +1,19 @@ +import {useState} from 'react'; + +import styles from './SelectButton.module.scss'; + +const SelectButton = () => { + const [isClicked, setIsClicked] = useState(false); + + const handleClick = () => { + setIsClicked((prev) => !prev); + }; + + return ( + + ); +}; + +export default SelectButton; From c56b4243eeb7f59dd23ecdd182d381fb1c0f2f63 Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sun, 21 Jan 2024 06:07:19 +0900 Subject: [PATCH 26/50] Feat: markup AddToCandidateButton --- .../AddToCandidateButton.tsx | 29 +++++++++++++++++++ .../SelectButton/SelectButton.tsx | 6 ++++ 2 files changed, 35 insertions(+) create mode 100644 src/components/ButtonsInAddingCandidate/AddToCandidateButton/AddToCandidateButton.tsx diff --git a/src/components/ButtonsInAddingCandidate/AddToCandidateButton/AddToCandidateButton.tsx b/src/components/ButtonsInAddingCandidate/AddToCandidateButton/AddToCandidateButton.tsx new file mode 100644 index 00000000..d59e43fa --- /dev/null +++ b/src/components/ButtonsInAddingCandidate/AddToCandidateButton/AddToCandidateButton.tsx @@ -0,0 +1,29 @@ +import {Button} from '@chakra-ui/react'; +import {useRecoilValue} from 'recoil'; + +import {selectedCandidatesState} from '@/recoil/vote/candidateList'; + +//prop이나 params로 trip or vote 판별, onClick에 삼항연산자로 함수 넣기 +const AddToCandidateButton = () => { + const selectedCandidates = useRecoilValue(selectedCandidatesState); + const counts = selectedCandidates.size; + // const navigate = useNavigate(); + + // const addCandidates = () => { + // // 경로 추후 수정 + // // navigate(`${pathname}/`, { replace: true }) + // }; + const trip = true; + + return ( + + ); +}; + +export default AddToCandidateButton; diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx index 3fe66de2..fd7d18a0 100644 --- a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx @@ -2,11 +2,17 @@ import {useState} from 'react'; import styles from './SelectButton.module.scss'; +import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; const SelectButton = () => { const [isClicked, setIsClicked] = useState(false); + const {addCandidateInSelectedList} = useGetSelectedCandidates(); + + //"선택된 장소의 ID" + const placeId = 22; const handleClick = () => { setIsClicked((prev) => !prev); + addCandidateInSelectedList(placeId); }; return ( From efcd845c3a854183e054fc0cb63ee1f650ca88f5 Mon Sep 17 00:00:00 2001 From: SKY-PEY Date: Sun, 21 Jan 2024 06:09:07 +0900 Subject: [PATCH 27/50] Conf: modify codes --- .../Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx | 1 + src/components/VoteMemo/MemoContent.tsx | 4 ++-- src/pages/Vote/VoteMemo/VoteMemo.tsx | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx b/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx index 94faa215..5dc2f9e9 100644 --- a/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx +++ b/src/components/Vote/DeleteCandidatesButton/DeleteCandidatesButton.tsx @@ -25,6 +25,7 @@ const DeleteCandidatesButton = () => { const deleteCandidate = () => { deleteCandidateMutation.mutate({voteId: Number(voteId), candidateId: [...selectedCandidates]}); + setSelectedCandidates(new Set()); }; const showDeleteCandidateModal = () => { diff --git a/src/components/VoteMemo/MemoContent.tsx b/src/components/VoteMemo/MemoContent.tsx index e995ad20..ad8629cc 100644 --- a/src/components/VoteMemo/MemoContent.tsx +++ b/src/components/VoteMemo/MemoContent.tsx @@ -13,13 +13,13 @@ const MemoContent = ({data}: {data: VoteInfo}) => { const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); const candidates = data.candidates; - const getCandidateIds = () => { + const CheckAllCandidates = () => { const newCandidateIds = candidates.map((candidate) => candidate.id); setSelectedCandidates(new Set(newCandidateIds)); }; useEffect(() => { - getCandidateIds(); + CheckAllCandidates(); }, []); return ( diff --git a/src/pages/Vote/VoteMemo/VoteMemo.tsx b/src/pages/Vote/VoteMemo/VoteMemo.tsx index 375ae05a..39eff02b 100644 --- a/src/pages/Vote/VoteMemo/VoteMemo.tsx +++ b/src/pages/Vote/VoteMemo/VoteMemo.tsx @@ -22,6 +22,9 @@ const VoteMemo = () => { const [isBTOpen, setIsBTOpen] = useRecoilState(isBottomSlideOpenState); const selectedCandidates = useRecoilValue(selectedCandidatesState); + //후보 추가하는 api, selectedCandidates 0으로 리셋, 내비게이트 + + // return (
    setIsBTOpen(true)} /> @@ -30,7 +33,7 @@ const VoteMemo = () => { From 4da9efb5380631601cda2421143eeb62747d2141 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Sun, 21 Jan 2024 14:43:58 +0900 Subject: [PATCH 28/50] Refactor: add dimmed in img & apply ellipsis --- src/pages/User/MySpace/MySpace.module.scss | 33 ++++++++-- src/pages/User/MySpace/MySpace.tsx | 72 +++++++++++++++++++++- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/src/pages/User/MySpace/MySpace.module.scss b/src/pages/User/MySpace/MySpace.module.scss index 750b2d51..447cf0ca 100644 --- a/src/pages/User/MySpace/MySpace.module.scss +++ b/src/pages/User/MySpace/MySpace.module.scss @@ -59,7 +59,6 @@ & > li { display: flex; - gap: 16px; height: 9rem; border: 1px solid $neutral200; @@ -67,26 +66,48 @@ } &__img { + position: relative; display: flex; justify-content: center; align-items: center; + flex-shrink: 0; width: 6.6rem; background-position: center; background-size: cover; + background-blend-mode: darken; border-radius: 16px 0px 0px 16px; - @include typography(button); - color: $neutral0; + & > span { + z-index: 100; + @include typography(button); + color: $neutral0; + } + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + border-radius: 16px 0px 0px 16px; + } } + &__content { - flex: 1; - padding: 20px 0; + flex: 1 1 auto; + padding: 20px 16px; + min-width: 0; &__title { - @include typography(titleSmall); margin-bottom: 4px; + + @include typography(titleSmall); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } + &__date { @include typography(bodySmall); color: $neutral400; diff --git a/src/pages/User/MySpace/MySpace.tsx b/src/pages/User/MySpace/MySpace.tsx index 784b9775..5a8c8c75 100644 --- a/src/pages/User/MySpace/MySpace.tsx +++ b/src/pages/User/MySpace/MySpace.tsx @@ -4,7 +4,7 @@ import styles from "./MySpace.module.scss"; import Header from "@/components/Auth/Header/Header"; -const data = [ +const upcomming = [ { id: "asdfasdf", title: "강릉, 속초, 전주, 여수 여행", @@ -63,11 +63,71 @@ const data = [ }, ]; +const outdated = [ + { + id: "asdfasdf1", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "asdfasds124", + title: "캐리비안베이, 로만바스 온천, 라마드 호텔, 에버랜드", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "asdf123as15", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "asdd12dddddb", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "asd123d1d11dv", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "1234asdaaac", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "1234asda12s", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, + { + id: "1234asdazza", + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023.12.15(수)", + endDate: "2023.12.19(일)", + dueDate: 0, + }, +]; + const tmpImage = "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc="; function MySpace() { const [tab, setTab] = useState("upcomming"); + const [data, setData] = useState(upcomming); return (
    @@ -81,6 +141,7 @@ function MySpace() { }`} onClick={() => { setTab("upcomming"); + setData(upcomming); }} > 다가오는 여행 @@ -90,6 +151,7 @@ function MySpace() { tab === "outdated" ? styles.selected : "" }`} onClick={() => { + setData(outdated); setTab("outdated"); }} > @@ -112,7 +174,13 @@ function MySpace() { backgroundImage: `url(${tmpImage})`, }} > - {dueDate === 0 ? "여행 중" : `D-${dueDate}`} + + {dueDate === 0 + ? tab === "upcomming" + ? "여행중" + : "" + : `D-${dueDate}`} +
    From 84707cb2dbe22058d1907c76aa66e2fac7d37ba5 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Sun, 21 Jan 2024 16:30:40 +0900 Subject: [PATCH 29/50] Feat: add my review page --- src/assets/icons/meatball.svg | 5 + src/assets/icons/star_fill.svg | 5 + src/pages/User/MyReview/MyReview.module.scss | 69 ++++++++ src/pages/User/MyReview/MyReview.tsx | 166 +++++++++++++++++++ src/pages/User/MySpace/MySpace.module.scss | 2 +- src/pages/User/User.tsx | 7 +- src/routes/MainRouter/MainRouter.tsx | 2 + 7 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/assets/icons/meatball.svg create mode 100644 src/assets/icons/star_fill.svg create mode 100644 src/pages/User/MyReview/MyReview.module.scss create mode 100644 src/pages/User/MyReview/MyReview.tsx diff --git a/src/assets/icons/meatball.svg b/src/assets/icons/meatball.svg new file mode 100644 index 00000000..2a6e7da7 --- /dev/null +++ b/src/assets/icons/meatball.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/star_fill.svg b/src/assets/icons/star_fill.svg new file mode 100644 index 00000000..e92e6779 --- /dev/null +++ b/src/assets/icons/star_fill.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/pages/User/MyReview/MyReview.module.scss b/src/pages/User/MyReview/MyReview.module.scss new file mode 100644 index 00000000..56043263 --- /dev/null +++ b/src/pages/User/MyReview/MyReview.module.scss @@ -0,0 +1,69 @@ +@use "@/sass" as *; + +.container { + margin-top: 80px; + margin-bottom: 72px; + color: $neutral900; + + .myreview { + & > li { + padding-bottom: 32px; + margin-bottom: 24px; + border-bottom: 1px solid $neutral200; + } + + &__header { + display: flex; + gap: 12px; + padding: 0 20px; + + &__img { + width: 4rem; + border-radius: 8px; + + background-position: center; + background-size: cover; + } + + &__text { + &__title { + @include typography(button); + } + &__category { + @include typography(captionSmall); + color: $neutral400; + } + } + + &__meatball { + flex: 1; + + display: flex; + justify-content: end; + align-items: center; + } + } + + &__info { + display: flex; + align-items: center; + gap: 2px; + margin-top: 16px; + padding: 0 20px; + + @include typography(captionSmall); + + &__visited { + padding-left: 6px; + color: $primary300; + } + } + + &__content { + padding: 0 20px; + margin: 8px 0; + + @include typography(bodySmall); + } + } +} diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx new file mode 100644 index 00000000..66b2c6e8 --- /dev/null +++ b/src/pages/User/MyReview/MyReview.tsx @@ -0,0 +1,166 @@ +import styles from "./MyReview.module.scss"; + +import Header from "@/components/Auth/Header/Header"; +import ReviewImageSlider from "@/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider"; + +import Meatball from "@/assets/icons/meatball.svg?react"; +import Star from "@/assets/icons/star_fill.svg?react"; + +const data = [ + { + id: "123", + place: { + area: "대전", + category: "맛집 · 서울", + title: "대전 성심당", + thumbnail: + "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", + }, + visitedAt: "2024년 1월 방문", + rating: "5.0", + content: + "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", + images: [ + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + ], + }, + { + id: "123", + place: { + area: "대전", + category: "맛집 · 서울", + title: "대전 성심당", + thumbnail: + "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", + }, + visitedAt: "2024년 1월 방문", + rating: "5.0", + content: + "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", + images: [ + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + ], + }, + { + id: "123", + place: { + area: "대전", + category: "맛집 · 서울", + title: "대전 성심당", + thumbnail: + "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", + }, + visitedAt: "2024년 1월 방문", + rating: "5.0", + content: + "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", + images: [ + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + ], + }, + { + id: "123", + place: { + area: "대전", + category: "맛집 · 서울", + title: "대전 성심당", + thumbnail: + "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", + }, + visitedAt: "2024년 1월 방문", + rating: "5.0", + content: + "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", + images: [ + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + ], + }, + { + id: "123", + place: { + area: "대전", + category: "맛집 · 서울", + title: "대전 성심당", + thumbnail: + "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", + }, + visitedAt: "2024년 1월 방문", + rating: "5.0", + content: + "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", + images: [ + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + "https://pds.joongang.co.kr/news/component/htmlphoto_mmdata/202301/02/8c47d46b-ef7f-4508-b451-b0ad96c9b672.jpg", + ], + }, +]; + +function MyReview() { + return ( +
    +
    + +
      + {data.map(({ id, place, visitedAt, rating, content, images }) => ( +
    • +
      +
      + +
      +
      + {place.title} +
      +
      + {place.category} +
      +
      + + +
      + + +
      + +
      +
      {rating}
      + +
      {visitedAt}
      +
      + +

      {content}

      + + {images && } +
    • + ))} +
    +
    + ); +} + +export default MyReview; diff --git a/src/pages/User/MySpace/MySpace.module.scss b/src/pages/User/MySpace/MySpace.module.scss index 447cf0ca..976cf350 100644 --- a/src/pages/User/MySpace/MySpace.module.scss +++ b/src/pages/User/MySpace/MySpace.module.scss @@ -18,7 +18,7 @@ height: auto; padding: 0; padding-bottom: 12px; - + cursor: pointer; @include typography(tabLabel); &__target { diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index fba4ad59..3104d596 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -66,7 +66,12 @@ function User() {
    내 여행 스페이스
    - diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index ae472742..b9b17def 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -14,6 +14,7 @@ import RegionSearch from "@/pages/RegionSearch/RegionSearch"; import SearchFromHome from "@/pages/SearchFromHome/SearchFromHome"; import Trip from "@/pages/Trip/Trip"; import ModifyProfile from "@/pages/User/ModifyProfile/ModifyProfile"; +import MyReview from "@/pages/User/MyReview/MyReview"; import MySpace from "@/pages/User/MySpace/MySpace"; import User from "@/pages/User/User"; import UserPrivacy from "@/pages/User/UserPrivacy/UserPrivacy"; @@ -31,6 +32,7 @@ function MainRouter() { } /> } /> } /> + } /> } /> } /> From 3bb5df52a6bb07b8b55e44bab9c3956f5c5888fe Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Sun, 21 Jan 2024 17:12:40 +0900 Subject: [PATCH 30/50] Refactor: change styles of myspace & myreview --- src/pages/User/User.module.scss | 14 +++++++++++--- src/pages/User/User.tsx | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/User/User.module.scss b/src/pages/User/User.module.scss index 2a7cd2e6..01ce719d 100644 --- a/src/pages/User/User.module.scss +++ b/src/pages/User/User.module.scss @@ -51,13 +51,15 @@ .mywork { display: flex; + align-items: center; gap: 8px; - padding: 0 20px; + margin: 0 20px; margin-bottom: 8px; + border: 1px solid $neutral200; + border-radius: 16px; + button { - border: 1px solid $primary100; - border-radius: 16px; padding: 16px; flex: 1; @@ -75,6 +77,12 @@ } } + .colBar { + width: 1px; + height: 5rem; + background-color: $neutral200; + } + .myworkTitle { @include typography(subTitle); } diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 3104d596..cf5c2d21 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -66,6 +66,8 @@ function User() {
    내 여행 스페이스
    +
    + @@ -25,7 +25,9 @@ function SwiperButton({ imageIndex, imageLength }: SwiperButtonPropsType) { swiper.slideNext(); }} style={ - imageIndex >= imageLength ? { display: "none" } : { display: "block" } + imageIndex >= imageLength - 1 + ? { display: "none" } + : { display: "block" } } > diff --git a/src/components/Detail/Main/ImageSwiper/SwiperIndex/SwiperIndex.tsx b/src/components/Detail/Main/ImageSwiper/SwiperIndex/SwiperIndex.tsx index 107e4218..5e00e357 100644 --- a/src/components/Detail/Main/ImageSwiper/SwiperIndex/SwiperIndex.tsx +++ b/src/components/Detail/Main/ImageSwiper/SwiperIndex/SwiperIndex.tsx @@ -5,7 +5,7 @@ import { SwiperIndexPropsType } from "@/types/detail"; function SwiperIndex({ imageIndex, imageLength }: SwiperIndexPropsType) { return (
    - {imageIndex} + {imageIndex + 1} / {imageLength}
    diff --git a/src/components/Detail/Main/SlideModal/SlideModal.module.scss b/src/components/Detail/Main/SlideModal/SlideModal.module.scss new file mode 100644 index 00000000..53f69361 --- /dev/null +++ b/src/components/Detail/Main/SlideModal/SlideModal.module.scss @@ -0,0 +1,26 @@ +@use "@/sass" as *; + +.slideIndex { + @include typography(button); + color: $neutral0; + position: absolute; + top: 17px; + left: 50%; + transform: translateX(-50%); +} + +.mainSlideContainer { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.bottomSlide { + .swiperSlide { + border: 1px solid $neutral0; + } + + .swiperSlideActive { + border: 1px solid $primary300; + } +} diff --git a/src/components/Detail/Main/SlideModal/SlideModal.tsx b/src/components/Detail/Main/SlideModal/SlideModal.tsx new file mode 100644 index 00000000..96a29621 --- /dev/null +++ b/src/components/Detail/Main/SlideModal/SlideModal.tsx @@ -0,0 +1,112 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import { Thumbs } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; + +// Import Swiper styles +// import "swiper/css"; +// import "swiper/css/thumbs"; +import styles from "./SlideModal.module.scss"; + +interface SlideModalProps { + isOpen: boolean; + onClose: () => void; + images: string[]; + imageIndex: number; + setImageIndex: React.Dispatch>; +} + +function SlideModal({ + isOpen, + onClose, + images, + imageIndex, + setImageIndex, +}: SlideModalProps) { + const [thumbsSwiper, setThumbsSwiper] = useState(); + const [swiperIndex, setSwiperIndex] = useState(0); + + useEffect(() => { + setSwiperIndex(imageIndex); + }); + + return ( + + + + + + + {swiperIndex + 1}/{images.length} + + + + console.log(swiper.activeIndex)} + modules={[Thumbs]} + className={styles.mainSlideContainer} + onActiveIndexChange={(swiperCore) => { + setSwiperIndex(swiperCore.activeIndex); + setImageIndex(swiperCore.activeIndex); + }} + > + {images && + images.map((image, i) => ( + + + + ))} + + + + + {images && + images.map((image, i) => ( + + + + ))} + + + + + ); +} + +export default SlideModal; From 2cb0b1a8a9d92fc7986ac135dc3c2adfb150b7f2 Mon Sep 17 00:00:00 2001 From: Yamyam-code Date: Sun, 21 Jan 2024 21:51:25 +0900 Subject: [PATCH 32/50] Refactor: change search state --- .../SearchFromHome/MapHeader/MapHeader.tsx | 21 +++-- .../SearchFromHome/SearchBar/SearchBar.tsx | 29 +++---- .../SearchFromHome/SearchHome/SearchHome.tsx | 9 +- .../SearchKeyword/SearchKeyword.tsx | 11 ++- .../LocationFilter/LocationFilter.tsx | 26 ++---- .../LocationFliterPage/LocationFliterPage.tsx | 17 ++-- .../SearchFromHome/SearchList/SearchList.tsx | 70 +++++----------- .../SearchList/Tabs/Tab/Tab.tsx | 35 ++++---- .../SearchFromHome/SearchList/Tabs/Tabs.tsx | 21 ++--- src/pages/SearchFromHome/SearchFromHome.tsx | 82 ++++++------------- src/types/home.ts | 8 ++ 11 files changed, 133 insertions(+), 196 deletions(-) diff --git a/src/components/SearchFromHome/MapHeader/MapHeader.tsx b/src/components/SearchFromHome/MapHeader/MapHeader.tsx index 5bf2c899..6d859ab3 100644 --- a/src/components/SearchFromHome/MapHeader/MapHeader.tsx +++ b/src/components/SearchFromHome/MapHeader/MapHeader.tsx @@ -4,20 +4,23 @@ import styles from './MapHeader.module.scss'; import BackIcon from '@/assets/homeIcons/search/backInHome.svg?react'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - keyword: string; - category: string; - searchLocation: string; - sort: string; - setMoveMap: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } -function MapHeader({keyword, setMoveMap, category, searchLocation, sort}: PropsType) { +function MapHeader({forSearch, setForSearch}: PropsType) { const navigate = useNavigate(); function offMap() { - setMoveMap('false'); - navigate(`/home/search?keyword=${keyword}&category=${category}&map=false&location=${searchLocation}&sort=${sort}`); + const before = forSearch; + before.map = 'false'; + setForSearch(before); + navigate( + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=false&location=${forSearch.location}&sort=${forSearch.sort}`, + ); } return ( @@ -25,7 +28,7 @@ function MapHeader({keyword, setMoveMap, category, searchLocation, sort}: PropsT - {keyword} + {forSearch.keyword}
    ); } diff --git a/src/components/SearchFromHome/SearchBar/SearchBar.tsx b/src/components/SearchFromHome/SearchBar/SearchBar.tsx index 895e5062..a1b84ac8 100644 --- a/src/components/SearchFromHome/SearchBar/SearchBar.tsx +++ b/src/components/SearchFromHome/SearchBar/SearchBar.tsx @@ -6,48 +6,49 @@ import styles from './SearchBar.module.scss'; import BackIcon from '@/assets/homeIcons/search/backInHome.svg?react'; import SearchIcon from '@/assets/homeIcons/search/searchIcon.svg?react'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - keyword: string; - category: string; - searchLocation: string; - sort: string; - setKeyword: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } interface InputBarType extends HTMLInputElement { focus: () => void; } -function SearchBar({setKeyword, keyword, category, searchLocation, sort}: PropsType) { +function SearchBar({forSearch, setForSearch}: PropsType) { const [inputValue, setInputValue] = useState(''); const navigate = useNavigate(); const inputBar = useRef(null); useEffect(() => { - setInputValue(keyword); + setInputValue(forSearch.keyword); if (inputBar.current) { inputBar.current.focus(); } - }, [keyword]); + }, [forSearch.keyword]); function handleInputValue(e: React.ChangeEvent) { setInputValue(e.target.value); } function search() { - setKeyword(inputValue); - navigate( - `/home/search?keyword=${inputValue}&category=${category}&map=false&location=${searchLocation}&sort=${sort}`, - ); + const beforeData = forSearch; + beforeData.keyword = inputValue; + setForSearch(beforeData); + navigate(`/home/search?keyword=${inputValue}&category=전체&map=false&location=전국&sort=등록순`); } function removeValue() { - if (keyword === '') { + if (forSearch.keyword === '') { navigate('/'); } else { navigate('/home/search'); setInputValue(''); - setKeyword(''); + const beforeData = forSearch; + beforeData.keyword = ''; + setForSearch(beforeData); } } diff --git a/src/components/SearchFromHome/SearchHome/SearchHome.tsx b/src/components/SearchFromHome/SearchHome/SearchHome.tsx index 45330897..a8c5d1b8 100644 --- a/src/components/SearchFromHome/SearchHome/SearchHome.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchHome.tsx @@ -3,16 +3,19 @@ import styles from './SearchHome.module.scss'; import HotItems from './HotItems/HotItems'; import SearchKeyword from './SearchKeyword/SearchKeyword'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - set: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } -function SearchHome({set}: PropsType) { +function SearchHome({forSearch, setForSearch}: PropsType) { return (

    인기 검색 키워드

    - +

    최근 30일간 인기 장소

    diff --git a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx index e836eaef..52f822e0 100644 --- a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx @@ -9,11 +9,14 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import {getData} from '@/mocks/handlers/home'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - set: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } -function SearchKeyword({set}: PropsType) { +function SearchKeyword({forSearch, setForSearch}: PropsType) { const [data, setData] = useState(); const [listWidth, setListWidth] = useState(0); const [slideLocation, setSlideLocation] = useState(0); @@ -41,7 +44,9 @@ function SearchKeyword({set}: PropsType) { }, [data, componentRef]); function searchKeyword(keyword: string) { - set(keyword); + const beforeData = forSearch; + beforeData.keyword = keyword; + setForSearch(beforeData); } return ( diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx index 9e4cfcfa..7a8565dd 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx @@ -5,16 +5,14 @@ import styles from './LocationFilter.module.scss'; import LocationFliterPage from './LocationFliterPage/LocationFliterPage'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - keyword: string; - category: string; - moveMap: string; - searchLocation: string; - sort: string; - setSearchLocation: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } -function LocationFilter({keyword, category, moveMap, searchLocation, sort, setSearchLocation}: PropsType) { +function LocationFilter({forSearch, setForSearch}: PropsType) { const [click, setClick] = useState(true); const [buttonName, setButtonName] = useState('전체 지역'); @@ -23,13 +21,13 @@ function LocationFilter({keyword, category, moveMap, searchLocation, sort, setSe } useEffect(() => { - const datas = searchLocation.split(' '); + const datas = forSearch.location.split(' '); if (datas[0] === '전국') { setButtonName('전체 지역'); } else { setButtonName(datas[0]); } - }, [searchLocation]); + }, [forSearch.location]); return ( <> @@ -45,15 +43,7 @@ function LocationFilter({keyword, category, moveMap, searchLocation, sort, setSe {buttonName}
    - + ); } diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx index 37660d7f..b69b440c 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx @@ -8,14 +8,13 @@ import BackIcon from '@/assets/homeIcons/search/backInHome.svg?react'; import PopularList from './PopularList/PopularList'; import SelectLocation from './SelectLocation/SelectLocation'; +import {ForSearchType} from '@/types/home'; + interface PropsType { click: boolean; - keyword: string; - category: string; - moveMap: string; - sort: string; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; handleClick: () => void; - setSearchLocation: React.Dispatch>; } interface AreaDataType { @@ -23,7 +22,7 @@ interface AreaDataType { sigunguCode: number; } -function LocationFliterPage({click, keyword, category, moveMap, sort, handleClick, setSearchLocation}: PropsType) { +function LocationFliterPage({forSearch, click, handleClick, setForSearch}: PropsType) { const [area, setArea] = useState('전국'); const [areaData, setAreaData] = useState(); const [sigungu, setSigungu] = useState('전체 지역'); @@ -33,9 +32,11 @@ function LocationFliterPage({click, keyword, category, moveMap, sort, handleClic const vh = window.innerHeight / 100; function submit() { - setSearchLocation(`${area} ${sigungu}`); + const beforeData = forSearch; + beforeData.location = `${area} ${sigungu}`; + setForSearch(beforeData); navigate( - `/home/search?keyword=${keyword}&category=${category}&map=${moveMap}&location=${area} ${sigungu}&sort=${sort}`, + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=${forSearch.map}&location=${area} ${sigungu}&sort=${forSearch.sort}`, ); handleClick(); } diff --git a/src/components/SearchFromHome/SearchList/SearchList.tsx b/src/components/SearchFromHome/SearchList/SearchList.tsx index f553de47..ec95735e 100644 --- a/src/components/SearchFromHome/SearchList/SearchList.tsx +++ b/src/components/SearchFromHome/SearchList/SearchList.tsx @@ -12,31 +12,14 @@ import MapButton from './MapButton/MapButton'; import SearchItem from './SearchItem/SearchItem'; import Tabs from './Tabs/Tabs'; -import {SearchItemType} from '@/types/home'; +import {ForSearchType, SearchItemType} from '@/types/home'; interface PropsType { - keyword: string; - category: string; - moveMap: string; - searchLocation: string; - sort: string; - setMoveMap: React.Dispatch>; - setCategory: React.Dispatch>; - setSearchLocation: React.Dispatch>; - setSort: React.Dispatch>; + forSearch: ForSearchType; + setForSearch: React.Dispatch>; } -function SearchList({ - keyword, - moveMap, - category, - searchLocation, - sort, - setMoveMap, - setCategory, - setSearchLocation, - setSort, -}: PropsType) { +function SearchList({forSearch, setForSearch}: PropsType) { const [data, setData] = useState(); const [filterData, setFilterData] = useState(); const [categoryChange, setCategoryChange] = useState(false); @@ -47,56 +30,47 @@ function SearchList({ getData('home/search/search', setData); const querystring = searchParams.get('category'); + const beforeData = forSearch; if (querystring) { - setCategory(querystring); + beforeData.category = querystring; + setForSearch(beforeData); } - }, [keyword, searchParams]); + }, [searchParams]); useEffect(() => { if (data) { - if (category !== '전체') { + if (forSearch.category !== '전체') { let filterData; - if (category === '맛집') { + if (forSearch.category === '맛집') { filterData = data.filter((data) => data.category === '음식점' || data.category === '카페'); } else { - filterData = data.filter((data) => data.category === category); + filterData = data.filter((data) => data.category === forSearch.category); } setFilterData(filterData); } else { setFilterData(data); } } - }, [data, category]); + }, [data, forSearch.category]); function onMap() { - setMoveMap('true'); - navigate(`/home/search?keyword=${keyword}&category=${category}&map=true&location=${searchLocation}&sort=${sort}`); + const beforeData = forSearch; + beforeData.map = 'true'; + setForSearch(beforeData); + navigate( + `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=true&location=${forSearch.location}&sort=${forSearch.sort}`, + ); } return ( -
    - - {moveMap === 'true' && filterData ? ( +
    + + {forSearch.map === 'true' && filterData ? ( ) : ( <>
    - +
      diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx index 669b58e3..78bc479a 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx @@ -2,27 +2,16 @@ import {useNavigate} from 'react-router-dom'; import styles from './Tab.module.scss'; +import {ForSearchType} from '@/types/home'; + interface PropsType { - setCategory: React.Dispatch>; - setCategoryChange: React.Dispatch>; - category: string; + forSearch: ForSearchType; thisCategory: string; - keyword: string; - moveMap: string; - searchLocation: string; - sort: string; + setCategoryChange: React.Dispatch>; + setForSearch: React.Dispatch>; } -function Tab({ - setCategory, - setCategoryChange, - category, - thisCategory, - keyword, - moveMap, - sort, - searchLocation, -}: PropsType) { +function Tab({forSearch, thisCategory, setForSearch, setCategoryChange}: PropsType) { const navigate = useNavigate(); function handleCategory(key: string) { @@ -30,8 +19,12 @@ function Tab({ setTimeout(() => { setCategoryChange(false); }, 150); - setCategory(key); - navigate(`/home/search?keyword=${keyword}&category=${key}&map=${moveMap}&location=${searchLocation}&sort=${sort}`); + const beforeData = forSearch; + beforeData.category = key; + setForSearch(beforeData); + navigate( + `/home/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}`, + ); } return ( @@ -39,8 +32,8 @@ function Tab({ className={styles.container} id={thisCategory} style={{ - color: category === thisCategory ? '#1d2433' : '#cdcfd0', - borderBottom: category === thisCategory ? '2px solid #1d2433' : 'none', + color: forSearch.category === thisCategory ? '#1d2433' : '#cdcfd0', + borderBottom: forSearch.category === thisCategory ? '2px solid #1d2433' : 'none', }} onClick={() => { handleCategory(thisCategory); diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx index 8340dd76..40a5bf70 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx @@ -8,18 +8,15 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import Tab from './Tab/Tab'; -interface PropsType { - keyword: string; - category: string; - moveMap: string; - searchLocation: string; - sort: string; +import {ForSearchType} from '@/types/home'; - setCategory: React.Dispatch>; +interface PropsType { + forSearch: ForSearchType; + setForSearch: React.Dispatch>; setCategoryChange: React.Dispatch>; } -function Tabs({setCategory, setCategoryChange, category, keyword, moveMap, searchLocation, sort}: PropsType) { +function Tabs({forSearch, setForSearch, setCategoryChange}: PropsType) { const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); const thisCategory = ['전체', '맛집', '숙소', '관광지', '문화시설', '레포츠', '쇼핑']; @@ -45,15 +42,11 @@ function Tabs({setCategory, setCategoryChange, category, keyword, moveMap, searc > {thisCategory.map((thisCategory) => ( ))}
    diff --git a/src/pages/SearchFromHome/SearchFromHome.tsx b/src/pages/SearchFromHome/SearchFromHome.tsx index adcbc086..ceef949b 100644 --- a/src/pages/SearchFromHome/SearchFromHome.tsx +++ b/src/pages/SearchFromHome/SearchFromHome.tsx @@ -11,11 +11,13 @@ import SearchHome from '@/components/SearchFromHome/SearchHome/SearchHome'; import SearchList from '@/components/SearchFromHome/SearchList/SearchList'; function SearchFromHome() { - const [keyword, setKeyword] = useState(''); - const [category, setCategory] = useState('전체'); - const [moveMap, setMoveMap] = useState('false'); - const [searchLocation, setSearchLocation] = useState('전국'); - const [sort, setSort] = useState('등록순'); + const [forSearch, setForSearch] = useState({ + keyword: '', + category: '전체', + map: 'false', + location: '전국', + sort: '등록순', + }); const [searchParams] = useSearchParams(); const vh = window.innerHeight; @@ -28,30 +30,16 @@ function SearchFromHome() { sort: searchParams.get('sort'), }; - let queryKeyword = ''; - let queryLocation = '전국'; - let querySort = '등록순'; - - if (querystring.keyword) { - queryKeyword = querystring.keyword; - setKeyword(querystring.keyword); - } - if (querystring.category) { - setCategory(querystring.category); - } - if (querystring.map) { - setMoveMap(querystring.map); + if (querystring.keyword && querystring.category && querystring.location && querystring.map && querystring.sort) { + setForSearch({ + keyword: querystring.keyword, + category: querystring.category, + map: querystring.map, + location: querystring.location, + sort: querystring.sort, + }); + console.log(search(querystring.keyword, querystring.location, querystring.sort)); } - if (querystring.location) { - queryLocation = querystring.location; - setSearchLocation(querystring.location); - } - if (querystring.sort) { - querySort = querystring.sort; - setSort(querystring.sort); - } - - console.log(search(queryKeyword, queryLocation, querySort)); }, [searchParams]); return ( @@ -59,41 +47,19 @@ function SearchFromHome() { className={styles.container} style={{ height: `${vh}px`, - gap: moveMap === 'true' ? '0' : '24px', - paddingTop: moveMap === 'true' ? '0' : '16px', + gap: forSearch.map === 'true' ? '0' : '24px', + paddingTop: forSearch.map === 'true' ? '0' : '16px', }} > - {moveMap === 'true' ? ( - + {forSearch.map === 'true' ? ( + ) : ( - + )} - {keyword === '' ? ( - + {forSearch.keyword === '' ? ( + ) : ( - + )}
    ); diff --git a/src/types/home.ts b/src/types/home.ts index 0e14100c..8126bba2 100644 --- a/src/types/home.ts +++ b/src/types/home.ts @@ -79,3 +79,11 @@ export interface SearchItemType { location: SearchItemLocationType; category: string; } + +export interface ForSearchType { + keyword: string; + category: string; + map: string; + location: string; + sort: string; +} From f98b0f42d48dbd9788158bc166a5318f4452a920 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Mon, 22 Jan 2024 01:53:52 +0900 Subject: [PATCH 33/50] Feat: review slide zoom modal --- .../ReviewImageSlider/ReviewImageSlider.tsx | 21 ++++++++++++++++-- .../Main/SlideModal/SlideModal.module.scss | 22 ++++++++++++++++++- .../Detail/Main/SlideModal/SlideModal.tsx | 3 +-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider.tsx b/src/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider.tsx index 6c8741d6..638652e9 100644 --- a/src/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider.tsx +++ b/src/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider.tsx @@ -1,15 +1,25 @@ +import { useDisclosure } from "@chakra-ui/react"; import { useState } from "react"; import styles from "./ReviewImageSlider.module.scss"; import useComponentSize from "@/hooks/useComponetSize"; +import SlideModal from "@/components/Detail/Main/SlideModal/SlideModal"; import SlideButton from "@/components/SlideButton/SlideButton"; function ReviewImageSlider({ images }: { images: string[] }) { const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); + const [imageIndex, setImageIndex] = useState(0); + const { isOpen, onOpen, onClose } = useDisclosure(); + + const handleIsOpen = (index: number) => { + setImageIndex(index); + onOpen(); + }; + return (
    {images && ( @@ -36,10 +46,17 @@ function ReviewImageSlider({ images }: { images: string[] }) { left: slideLocation + "px", }} > - {images.map((data) => ( - + {images.map((data, i) => ( + handleIsOpen(i)} /> ))}
    +
    ); } diff --git a/src/components/Detail/Main/SlideModal/SlideModal.module.scss b/src/components/Detail/Main/SlideModal/SlideModal.module.scss index 53f69361..414cdef7 100644 --- a/src/components/Detail/Main/SlideModal/SlideModal.module.scss +++ b/src/components/Detail/Main/SlideModal/SlideModal.module.scss @@ -10,17 +10,37 @@ } .mainSlideContainer { - position: absolute; + position: fixed; top: 50%; transform: translateY(-50%); + + div { + position: relative; + height: 50rem; + img { + position: absolute; + top: 50%; + transform: translateY(-50%); + } + } } .bottomSlide { + width: 100%; .swiperSlide { border: 1px solid $neutral0; + width: 5.6rem; + height: 5.6rem; } .swiperSlideActive { border: 1px solid $primary300; + width: 5.6rem; + height: 5.6rem; + } + + img { + width: 100%; + height: 100%; } } diff --git a/src/components/Detail/Main/SlideModal/SlideModal.tsx b/src/components/Detail/Main/SlideModal/SlideModal.tsx index 96a29621..c855922a 100644 --- a/src/components/Detail/Main/SlideModal/SlideModal.tsx +++ b/src/components/Detail/Main/SlideModal/SlideModal.tsx @@ -59,7 +59,6 @@ function SlideModal({ Date: Mon, 22 Jan 2024 01:55:21 +0900 Subject: [PATCH 34/50] Fix: useEffect error in slide moal --- src/components/Detail/Main/SlideModal/SlideModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Detail/Main/SlideModal/SlideModal.tsx b/src/components/Detail/Main/SlideModal/SlideModal.tsx index c855922a..ca9f8438 100644 --- a/src/components/Detail/Main/SlideModal/SlideModal.tsx +++ b/src/components/Detail/Main/SlideModal/SlideModal.tsx @@ -38,7 +38,7 @@ function SlideModal({ useEffect(() => { setSwiperIndex(imageIndex); - }); + }, [imageIndex]); return ( From 5672e53b7844b08652aea541da0088287d8519c6 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Mon, 22 Jan 2024 02:08:26 +0900 Subject: [PATCH 35/50] Feat: create init bottom slide feature --- .../BottomSlideDetail/BottomSlideDetail.tsx | 3 +++ src/pages/Detail/Detail.tsx | 18 +++++++++++++++--- src/types/detail.ts | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/Detail/BottomSlideDetail/BottomSlideDetail.tsx b/src/components/Detail/BottomSlideDetail/BottomSlideDetail.tsx index 901601df..ed2468a2 100644 --- a/src/components/Detail/BottomSlideDetail/BottomSlideDetail.tsx +++ b/src/components/Detail/BottomSlideDetail/BottomSlideDetail.tsx @@ -12,6 +12,7 @@ function BottomSlideDetail({ onClose, children, isReviewModal, + setBottomSlideContent, }: BottomSlideDetailProps) { const setIsModalOpen = useSetRecoilState(isModalOpenState); const setModalContent = useSetRecoilState(modalContentState); @@ -23,6 +24,7 @@ function BottomSlideDetail({ actionButton: "나갈래요", isSmallSize: true, onClickAction: () => { + setBottomSlideContent(null); setIsModalOpen(false); onClose(); document.body.style.removeProperty("overflow"); @@ -45,6 +47,7 @@ function BottomSlideDetail({ if (isReviewModal) { showCheckBeforeExitModal(); } else { + setBottomSlideContent(null); onClose(); } document.body.style.removeProperty("overflow"); diff --git a/src/pages/Detail/Detail.tsx b/src/pages/Detail/Detail.tsx index 7a0a532c..a877af7f 100644 --- a/src/pages/Detail/Detail.tsx +++ b/src/pages/Detail/Detail.tsx @@ -30,6 +30,11 @@ function Detail() { document.body.style.overflow = "hidden"; }; + const handleSlideOnClose = () => { + setBottomSlideContent(null); + onClose(); + }; + return (
    , false, ) @@ -46,12 +51,18 @@ function Detail() {
    - onBottomSlideOpen(, true) + onBottomSlideOpen( + , + true, + ) } /> - onBottomSlideOpen(, false) + onBottomSlideOpen( + , + false, + ) } />
    diff --git a/src/types/detail.ts b/src/types/detail.ts index bfeb50b0..40034893 100644 --- a/src/types/detail.ts +++ b/src/types/detail.ts @@ -98,4 +98,5 @@ export interface BottomSlideDetailProps { onClose: () => void; children: ReactNode; isReviewModal: boolean; + setBottomSlideContent: React.Dispatch>; } From ccc600b41d12185acc2ed80c3d4efed5bb03fd1a Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Mon, 22 Jan 2024 02:11:58 +0900 Subject: [PATCH 36/50] Feat: kakao map draggable, zoomable false --- .../Information/BasicInformation/MapInDetail/MapInDetail.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx b/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx index 7454438e..7a6bcd5a 100644 --- a/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx +++ b/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx @@ -36,6 +36,8 @@ function MapInDetail() { From 002363072a90fdd8389d7f7a669d0fa48b368942 Mon Sep 17 00:00:00 2001 From: JinJeongMin Date: Mon, 22 Jan 2024 03:25:33 +0900 Subject: [PATCH 37/50] Feat: create map drawer --- .../MapInDetail/MapInDetail.tsx | 42 +++++++--- .../MapModal/MapModal.module.scss | 83 +++++++++++++++++++ .../BasicInformation/MapModal/MapModal.tsx | 68 +++++++++++++++ ...Rviews.module.scss => Reviews.module.scss} | 0 .../Detail/Contents/Reviews/Reviews.tsx | 2 +- 5 files changed, 184 insertions(+), 11 deletions(-) create mode 100644 src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.module.scss create mode 100644 src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.tsx rename src/components/Detail/Contents/Reviews/{Rviews.module.scss => Reviews.module.scss} (100%) diff --git a/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx b/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx index 7a6bcd5a..b6fcfb1a 100644 --- a/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx +++ b/src/components/Detail/Contents/Information/BasicInformation/MapInDetail/MapInDetail.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { useDisclosure } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import { CustomOverlayMap, Map } from "react-kakao-maps-sdk"; @@ -7,6 +8,8 @@ import styles from "./MapInDetail.module.scss"; import BigHomeMarker from "@/assets/homeIcons/map/house_big.svg?react"; +import MapModal from "../MapModal/MapModal"; + interface Coordinate { lat: number; lng: number; @@ -18,6 +21,7 @@ function MapInDetail() { lat: 33.5563, lng: 126.79581, }); + const { isOpen, onOpen, onClose } = useDisclosure(); useEffect(() => { const geocoder = new kakao.maps.services.Geocoder(); @@ -32,17 +36,35 @@ function MapInDetail() { geocoder.addressSearch("경기도 양평군 양평읍 백안리 9", callback); }, []); + const handleMapDoubleClick = (event: any) => { + event.preventDefault(); + }; + return ( - - - - - + <> + + + + + + + ); } diff --git a/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.module.scss b/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.module.scss new file mode 100644 index 00000000..22c73749 --- /dev/null +++ b/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.module.scss @@ -0,0 +1,83 @@ +@use "@/sass" as *; + +.header { + position: fixed; + width: 100%; + + padding: 16px 20px; + background-color: $neutral0; + + display: flex; + align-items: center; + gap: 12px; + + @include typography(button); + color: $neutral900; + + z-index: 10; +} + +.body { + width: 100%; + + .map { + width: 100%; + height: 100%; + } +} + +.footer { + position: fixed; + width: 100%; + padding: 24px 20px; + bottom: 0; + + z-index: 10; + + .flexBox { + width: 100%; + display: flex; + } + + &__card { + width: 100%; + height: 12rem; + background-color: $neutral0; + border-radius: 16px; + + display: flex; + align-items: center; + gap: 12px; + + max-width: 41rem; + + position: relative; + + img { + width: 9.6rem; + height: 9.6rem; + } + + &__textWrapper { + display: flex; + flex-direction: column; + gap: 4px; + justify-content: center; + + &__name { + @include typography(titleSmall); + color: $neutral900; + } + &__category { + @include typography(captionSmall); + color: $neutral400; + } + } + + &__icon { + position: absolute; + top: 16px; + right: 16px; + } + } +} diff --git a/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.tsx b/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.tsx new file mode 100644 index 00000000..43a55c1a --- /dev/null +++ b/src/components/Detail/Contents/Information/BasicInformation/MapModal/MapModal.tsx @@ -0,0 +1,68 @@ +import { + Drawer, + DrawerBody, + DrawerContent, + DrawerFooter, + DrawerHeader, +} from "@chakra-ui/react"; +import { AiOutlineLeft } from "react-icons/ai"; +import { FaRegHeart } from "react-icons/fa"; +import { CustomOverlayMap, Map } from "react-kakao-maps-sdk"; + +import styles from "./MapModal.module.scss"; + +import BigHomeMarker from "@/assets/homeIcons/map/house_big.svg?react"; + +interface MapModalProps { + isOpen: boolean; + onClose: () => void; + lat: number; + lng: number; + name: string; +} + +function MapModal({ isOpen, onClose, lat, lng, name }: MapModalProps) { + return ( + + + + + {name} + + + + + + + + + + + +
    +
    + +
    +

    {name}

    +

    + 숙소 서울 +

    +
    + {" "} +
    +
    +
    +
    +
    + ); +} + +export default MapModal; diff --git a/src/components/Detail/Contents/Reviews/Rviews.module.scss b/src/components/Detail/Contents/Reviews/Reviews.module.scss similarity index 100% rename from src/components/Detail/Contents/Reviews/Rviews.module.scss rename to src/components/Detail/Contents/Reviews/Reviews.module.scss diff --git a/src/components/Detail/Contents/Reviews/Reviews.tsx b/src/components/Detail/Contents/Reviews/Reviews.tsx index bf48dbd9..c6af09e3 100644 --- a/src/components/Detail/Contents/Reviews/Reviews.tsx +++ b/src/components/Detail/Contents/Reviews/Reviews.tsx @@ -2,7 +2,7 @@ import { CiEdit } from "react-icons/ci"; import { GoStarFill } from "react-icons/go"; import { useRecoilValue, useSetRecoilState } from "recoil"; -import styles from "./Rviews.module.scss"; +import styles from "./Reviews.module.scss"; import Review from "@/components/Detail/Contents/Review/Review"; From 29245577aea2f78c7254ea5999252f80c04eac16 Mon Sep 17 00:00:00 2001 From: Yamyam-code Date: Mon, 22 Jan 2024 04:48:34 +0900 Subject: [PATCH 38/50] Feat: search connect api --- src/api/search.ts | 27 + .../RecommendedLocation.tsx | 43 +- .../RecommendedLocationList.tsx | 32 +- .../Home/TripSpaceAtHome/TripSpaceAtHome.tsx | 30 +- src/components/Home/VoteAtHome/VoteAtHome.tsx | 14 +- .../SearchFromHome/SearchBar/SearchBar.tsx | 2 +- .../SearchHome/HotItems/HotItems.tsx | 42 +- .../SearchFromHome/SearchHome/SearchHome.tsx | 15 +- .../SearchKeyword/SearchKeyword.tsx | 30 +- .../LocationFilter/LocationFilter.tsx | 5 +- .../LocationFliterPage/LocationFliterPage.tsx | 23 +- .../SearchFromHome/SearchList/Map/Map.tsx | 79 +-- .../SearchFromHome/SearchList/SearchList.tsx | 28 +- .../SearchList/Tabs/Tab/Tab.tsx | 16 +- .../SearchFromHome/SearchList/Tabs/Tabs.tsx | 4 +- src/hooks/Search/useSearch.ts | 78 ++- src/mocks/handlers/home.ts | 163 +---- src/pages/SearchFromHome/SearchFromHome.tsx | 9 +- src/types/home.ts | 2 +- src/utils/categories.json | 610 ++++++++++++++++++ src/utils/contentType.json | 30 + 21 files changed, 959 insertions(+), 323 deletions(-) create mode 100644 src/api/search.ts create mode 100644 src/utils/categories.json create mode 100644 src/utils/contentType.json diff --git a/src/api/search.ts b/src/api/search.ts new file mode 100644 index 00000000..5df432bc --- /dev/null +++ b/src/api/search.ts @@ -0,0 +1,27 @@ +import axios from 'axios'; + +import {SearchItemType} from '@/types/home'; + +export async function getSearchData( + page: number, + areaCode: number, + sigunguCode: number, + placeTypeId: number, + keyword: string, + sort: string, + categoryCode: string, +) { + const searchData: SearchItemType = await axios.get('/api/places/search', { + params: { + page: page, + size: 20, + areaCode: areaCode, + sigunguCode: sigunguCode, + placeTypeId: placeTypeId, + keyword: keyword, + sort: sort, + categoryCode: categoryCode, + }, + }); + return searchData; +} diff --git a/src/components/Home/RecommendedLocationList/RecommendedLocation/RecommendedLocation.tsx b/src/components/Home/RecommendedLocationList/RecommendedLocation/RecommendedLocation.tsx index 0b3a14b6..82aa2658 100644 --- a/src/components/Home/RecommendedLocationList/RecommendedLocation/RecommendedLocation.tsx +++ b/src/components/Home/RecommendedLocationList/RecommendedLocation/RecommendedLocation.tsx @@ -1,23 +1,48 @@ -import { Link } from "react-router-dom"; +import {Link} from 'react-router-dom'; -import styles from "./RecommendedLocation.module.scss"; +import styles from './RecommendedLocation.module.scss'; -import { LocationDataType } from "@/types/home"; +import {LocationDataType} from '@/types/home'; interface PropsType { data: LocationDataType; } -function RecommendedLocation(data: PropsType) { - const link = `/home/search`; - const imageAlt = `${data.data.location}의 사진`; +function RecommendedLocation({data}: PropsType) { + let location: string; + + switch (data.location) { + case '제주': + location = '제주 전체'; + break; + case '부산': + location = '부산 전체'; + break; + case '강릉': + location = '강원 강릉시'; + break; + case '속초': + location = '강원 속초시'; + break; + case '경주': + location = '경북 경주시'; + break; + case '여수': + location = '전남 여수시'; + break; + case '전주': + location = '전북 전주시'; + break; + default: + location = '전국'; + } return (
    - - {imageAlt} + + {`${data.location}의
    - {data.data.location} + {data.location}
    ); diff --git a/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx b/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx index bbe59669..03dfcc72 100644 --- a/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx +++ b/src/components/Home/RecommendedLocationList/RecommendedLocationList.tsx @@ -8,29 +8,45 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import RecommendedLocation from './RecommendedLocation/RecommendedLocation'; -import {LocationDataType} from '@/types/home'; - function RecommendedLocationList() { - const [data, setData] = useState(); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); const recommendedLocation = [ { location: '제주', - imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + imageURL: + 'https://images.unsplash.com/photo-1579169326371-ccb4e63f7889?q=80&w=1587&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', }, { location: '부산', - imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + imageURL: + 'https://images.unsplash.com/photo-1578037571214-25e07ed4a487?q=80&w=2808&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', }, { location: '강릉', - imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + imageURL: + 'https://images.unsplash.com/photo-1621044332832-717d5d986ab7?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + }, + { + location: '속초', + imageURL: + 'https://images.unsplash.com/photo-1663949405336-e142646d9fbd?q=80&w=2697&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + }, + { + location: '경주', + imageURL: + 'https://images.unsplash.com/photo-1656980593245-b54c8c0828f0?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + }, + { + location: '여수', + imageURL: + 'https://images.unsplash.com/photo-1651375562199-65caae096ace?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', }, { - location: '서울', - imageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + location: '전주', + imageURL: + 'https://images.unsplash.com/photo-1544827503-673e2a2c4c00?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MXx8JUVDJUEwJTg0JUVDJUEzJUJDfGVufDB8fDB8fHww', }, ]; diff --git a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx index aab13e8b..59d95c92 100644 --- a/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx +++ b/src/components/Home/TripSpaceAtHome/TripSpaceAtHome.tsx @@ -1,16 +1,16 @@ -import { useEffect, useState } from "react"; +import {useEffect, useState} from 'react'; -import styles from "./TripSpaceAtHome.module.scss"; +import styles from './TripSpaceAtHome.module.scss'; -import useComponentSize from "@/hooks/useComponetSize"; +import useComponentSize from '@/hooks/useComponetSize'; -import SlideButton from "@/components/SlideButton/SlideButton"; +import SlideButton from '@/components/SlideButton/SlideButton'; -import { getData } from "@/mocks/handlers/home"; +import {getData} from '@/mocks/handlers/home'; -import TripSpaceItem from "./TripSpaceItem/TripSpaceItem"; +import TripSpaceItem from './TripSpaceItem/TripSpaceItem'; -import { TripSpaceDataType } from "@/types/home"; +import {TripSpaceDataType} from '@/types/home'; function TripSpaceAtHome() { const [data, setData] = useState(); @@ -18,14 +18,14 @@ function TripSpaceAtHome() { const [componentRef, size] = useComponentSize(); const dataNull = { - tripTitle: "아직 여행 일정이 없어요", - tripDay: "새로운 여행 일정을 만들어보세요!", - tripImg: "/tripVoteLogoHome.png", + tripTitle: '아직 여행 일정이 없어요', + tripDay: '새로운 여행 일정을 만들어보세요!', + tripImg: '/tripVoteLogoHome.png', dDay: undefined, }; useEffect(() => { - getData(`home/tripSpace`, setData); + getData(`api/home/tripSpace`, setData); }, []); return ( @@ -44,14 +44,12 @@ function TripSpaceAtHome() { className={styles.slide_box} ref={componentRef} style={{ - overflow: size.width < 449 ? "scroll" : "visible", - left: slideLocation + "px", + overflow: size.width < 449 ? 'scroll' : 'visible', + left: slideLocation + 'px', }} > {data ? ( - data.map((data, i) => ( - - )) + data.map((data, i) => ) ) : ( )} diff --git a/src/components/Home/VoteAtHome/VoteAtHome.tsx b/src/components/Home/VoteAtHome/VoteAtHome.tsx index ed1613ed..a36928ad 100644 --- a/src/components/Home/VoteAtHome/VoteAtHome.tsx +++ b/src/components/Home/VoteAtHome/VoteAtHome.tsx @@ -1,19 +1,19 @@ -import { useEffect, useState } from "react"; +import {useEffect, useState} from 'react'; -import styles from "./VoteAtHome.module.scss"; +import styles from './VoteAtHome.module.scss'; -import { getData } from "@/mocks/handlers/home"; +import {getData} from '@/mocks/handlers/home'; -import CardHaveVote from "./VoteCard/CardHaveVote/CardHaveVote"; -import CardNull from "./VoteCard/CardNull/CardNull"; +import CardHaveVote from './VoteCard/CardHaveVote/CardHaveVote'; +import CardNull from './VoteCard/CardNull/CardNull'; -import { VoteDataType } from "@/types/home"; +import {VoteDataType} from '@/types/home'; function VoteAtHome() { const [data, setData] = useState(); useEffect(() => { - getData(`home/vote`, setData); + getData(`api/home/vote`, setData); }, []); return ( diff --git a/src/components/SearchFromHome/SearchBar/SearchBar.tsx b/src/components/SearchFromHome/SearchBar/SearchBar.tsx index a1b84ac8..2fd6207a 100644 --- a/src/components/SearchFromHome/SearchBar/SearchBar.tsx +++ b/src/components/SearchFromHome/SearchBar/SearchBar.tsx @@ -37,7 +37,7 @@ function SearchBar({forSearch, setForSearch}: PropsType) { const beforeData = forSearch; beforeData.keyword = inputValue; setForSearch(beforeData); - navigate(`/home/search?keyword=${inputValue}&category=전체&map=false&location=전국&sort=등록순`); + navigate(`/home/search?keyword=${inputValue}&category=0&map=false&location=${forSearch.location}&sort=등록순`); } function removeValue() { diff --git a/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx b/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx index d3936be4..db7421be 100644 --- a/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx +++ b/src/components/SearchFromHome/SearchHome/HotItems/HotItems.tsx @@ -1,30 +1,41 @@ -import { useEffect, useState } from "react"; +import axios from 'axios'; +import {Dispatch, useEffect, useState} from 'react'; -import styles from "./HotItems.module.scss"; +import styles from './HotItems.module.scss'; -import useComponentSize from "@/hooks/useComponetSize"; +import useComponentSize from '@/hooks/useComponetSize'; -import SlideButton from "@/components/SlideButton/SlideButton"; +import SlideButton from '@/components/SlideButton/SlideButton'; -import { getData } from "@/mocks/handlers/home"; +import HotItem from './HotItem/HotItem'; -import HotItem from "./HotItem/HotItem"; - -import { SearchHotItemType } from "@/types/home"; +import {SearchHotItemType} from '@/types/home'; interface PropsType { - type: string; + type: number; } -function HotItems({ type }: PropsType) { +function HotItems({type}: PropsType) { const [data, setData] = useState(); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); useEffect(() => { - getData(`places/popular`, setData); + async function getData(apiURL: string, set: Dispatch>) { + try { + const fetchData = await axios.get(`${apiURL}`, { + params: { + size: 10, + placeTypeId: type, + }, + }); + set(fetchData.data); + } catch (error) { + console.log(error); + } + } + getData('api/places/popular', setData); }, [type]); - return (
    {data && ( @@ -41,12 +52,11 @@ function HotItems({ type }: PropsType) { className={styles.slide_box} ref={componentRef} style={{ - overflow: size.width < 449 ? "scroll" : "visible", - left: slideLocation + "px", + overflow: size.width < 449 ? 'scroll' : 'visible', + left: slideLocation + 'px', }} > - {data && - data.map((data, i) => )} + {data && data.map((data, i) => )}
    ); diff --git a/src/components/SearchFromHome/SearchHome/SearchHome.tsx b/src/components/SearchFromHome/SearchHome/SearchHome.tsx index a8c5d1b8..0dcb6628 100644 --- a/src/components/SearchFromHome/SearchHome/SearchHome.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchHome.tsx @@ -3,27 +3,24 @@ import styles from './SearchHome.module.scss'; import HotItems from './HotItems/HotItems'; import SearchKeyword from './SearchKeyword/SearchKeyword'; -import {ForSearchType} from '@/types/home'; - -interface PropsType { - forSearch: ForSearchType; - setForSearch: React.Dispatch>; +interface Propstype { + setKeywordClick: React.Dispatch>; } -function SearchHome({forSearch, setForSearch}: PropsType) { +function SearchHome({setKeywordClick}: Propstype) { return (

    인기 검색 키워드

    - +

    최근 30일간 인기 장소

    - +

    최근 30일간 인기 숙소

    - +
    ); diff --git a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx index 52f822e0..9e28494c 100644 --- a/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx +++ b/src/components/SearchFromHome/SearchHome/SearchKeyword/SearchKeyword.tsx @@ -9,22 +9,19 @@ import SlideButton from '@/components/SlideButton/SlideButton'; import {getData} from '@/mocks/handlers/home'; -import {ForSearchType} from '@/types/home'; - -interface PropsType { - forSearch: ForSearchType; - setForSearch: React.Dispatch>; +interface Propstype { + setKeywordClick: React.Dispatch>; } -function SearchKeyword({forSearch, setForSearch}: PropsType) { - const [data, setData] = useState(); +function SearchKeyword({setKeywordClick}: Propstype) { + const [data, setData] = useState<{name: string; code: string}[] | undefined>(); const [listWidth, setListWidth] = useState(0); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); const navigate = useNavigate(); useEffect(() => { - getData('home/search/keyword', setData); + getData<{name: string; code: string}[] | undefined>('api/places/popular/keywords', setData); }, []); // 각 키워드의 너비를 모두 더한 값을 구함 @@ -43,12 +40,6 @@ function SearchKeyword({forSearch, setForSearch}: PropsType) { }, 100); }, [data, componentRef]); - function searchKeyword(keyword: string) { - const beforeData = forSearch; - beforeData.keyword = keyword; - setForSearch(beforeData); - } - return (
    {data && ( @@ -73,13 +64,16 @@ function SearchKeyword({forSearch, setForSearch}: PropsType) { {data ? ( data.map((keyword, i) => (

    { - searchKeyword(keyword); - navigate(`/home/search?keyword=${keyword}&category=전체&map=false&location=전국&sort=등록순`); + setKeywordClick(true); + setTimeout(() => { + setKeywordClick(false); + }, 2000); + navigate(`/home/search?keyword=${keyword.name}&category=0&map=false&location=전국&sort=등록순`); }} > - {keyword} + {keyword.name}

    )) ) : ( diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx index 7a8565dd..95e489ec 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFilter.tsx @@ -9,10 +9,9 @@ import {ForSearchType} from '@/types/home'; interface PropsType { forSearch: ForSearchType; - setForSearch: React.Dispatch>; } -function LocationFilter({forSearch, setForSearch}: PropsType) { +function LocationFilter({forSearch}: PropsType) { const [click, setClick] = useState(true); const [buttonName, setButtonName] = useState('전체 지역'); @@ -43,7 +42,7 @@ function LocationFilter({forSearch, setForSearch}: PropsType) { {buttonName}
    - + ); } diff --git a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx index b69b440c..db343e43 100644 --- a/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx +++ b/src/components/SearchFromHome/SearchList/LocationFilter/LocationFliterPage/LocationFliterPage.tsx @@ -1,4 +1,4 @@ -import {useState} from 'react'; +import {useEffect, useState} from 'react'; import {useNavigate} from 'react-router-dom'; import styles from './LocationFliterPage.module.scss'; @@ -13,7 +13,6 @@ import {ForSearchType} from '@/types/home'; interface PropsType { click: boolean; forSearch: ForSearchType; - setForSearch: React.Dispatch>; handleClick: () => void; } @@ -22,7 +21,7 @@ interface AreaDataType { sigunguCode: number; } -function LocationFliterPage({forSearch, click, handleClick, setForSearch}: PropsType) { +function LocationFliterPage({forSearch, click, handleClick}: PropsType) { const [area, setArea] = useState('전국'); const [areaData, setAreaData] = useState(); const [sigungu, setSigungu] = useState('전체 지역'); @@ -31,10 +30,13 @@ function LocationFliterPage({forSearch, click, handleClick, setForSearch}: Props const vh = window.innerHeight / 100; + useEffect(() => { + const locationData = forSearch.location.split(' '); + setArea(locationData[0]); + setSigungu(locationData[1]); + }, [forSearch.location]); + function submit() { - const beforeData = forSearch; - beforeData.location = `${area} ${sigungu}`; - setForSearch(beforeData); navigate( `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=${forSearch.map}&location=${area} ${sigungu}&sort=${forSearch.sort}`, ); @@ -52,7 +54,14 @@ function LocationFliterPage({forSearch, click, handleClick, setForSearch}: Props }} >
    -
    diff --git a/src/components/SearchFromHome/SearchList/Map/Map.tsx b/src/components/SearchFromHome/SearchList/Map/Map.tsx index 4d1c5ec3..10e974fd 100644 --- a/src/components/SearchFromHome/SearchList/Map/Map.tsx +++ b/src/components/SearchFromHome/SearchList/Map/Map.tsx @@ -1,26 +1,26 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect, useState } from "react"; +import {useEffect, useState} from 'react'; -import styles from "./Map.module.scss"; +import styles from './Map.module.scss'; -import flagMarker from "@/assets/homeIcons/map/flag.svg"; -import bigFlagMarker from "@/assets/homeIcons/map/flag_big.svg"; -import homeMarker from "@/assets/homeIcons/map/house.svg"; -import bigHomeMarker from "@/assets/homeIcons/map/house_big.svg"; -import forkMarker from "@/assets/homeIcons/map/restaurant.svg"; -import bigForkMarker from "@/assets/homeIcons/map/restaurant_big.svg"; +import flagMarker from '@/assets/homeIcons/map/flag.svg'; +import bigFlagMarker from '@/assets/homeIcons/map/flag_big.svg'; +import homeMarker from '@/assets/homeIcons/map/house.svg'; +import bigHomeMarker from '@/assets/homeIcons/map/house_big.svg'; +import forkMarker from '@/assets/homeIcons/map/restaurant.svg'; +import bigForkMarker from '@/assets/homeIcons/map/restaurant_big.svg'; -import MapItems from "./MapItems/MapItems"; +import MapItems from './MapItems/MapItems'; -import { SearchItemType } from "@/types/home"; +import {SearchItemType} from '@/types/home'; interface PropsType { data: SearchItemType[]; categoryChange: boolean; } -function Map({ data, categoryChange }: PropsType) { +function Map({data, categoryChange}: PropsType) { const [slideLocation, setSlideLocation] = useState(0); const [throttlePermission, setThrottlePermission] = useState(false); const [currentpin, setCurrentPin] = useState(); @@ -37,34 +37,22 @@ function Map({ data, categoryChange }: PropsType) { offSetHeight: number, data: SearchItemType, ) { - const image = - data.category === "숙소" - ? homeMarker - : data.category === "맛집" - ? forkMarker - : flagMarker; + const image = data.contentTypeId === 32 ? homeMarker : data.contentTypeId === 39 ? forkMarker : flagMarker; const imageSize = new window.kakao.maps.Size(sizeWidth, sizeHeight); const imageOption = { offset: new window.kakao.maps.Point(offSetWidth, offSetHeight), }; - const markerImage = new window.kakao.maps.MarkerImage( - image, - imageSize, - imageOption, - ); + const markerImage = new window.kakao.maps.MarkerImage(image, imageSize, imageOption); const marker = new window.kakao.maps.Marker({ - position: new window.kakao.maps.LatLng( - data.location.latitude, - data.location.longtitude, - ), + position: new window.kakao.maps.LatLng(data.location.latitude, data.location.longtitude), image: markerImage, }); return marker; } // 핀 클릭 이벤트 function clickMarker(marker: any, index: number) { - new window.kakao.maps.event.addListener(marker, "click", function () { + new window.kakao.maps.event.addListener(marker, 'click', function () { let timeOutId; if (window.innerWidth < 450) { if (timeOutId) { @@ -74,10 +62,10 @@ function Map({ data, categoryChange }: PropsType) { timeOutId = setTimeout(() => { setThrottlePermission(false); }, 2000); - const slide = document.querySelector("#map_slide"); + const slide = document.querySelector('#map_slide'); slide?.scrollTo({ left: 343 * index - 20, - behavior: "smooth", + behavior: 'smooth', }); } else { setSlideLocation(-343 * index + 20); @@ -90,16 +78,7 @@ function Map({ data, categoryChange }: PropsType) { const currentMarkers: any[] = []; data.map((data, i) => { - const marker = createPin( - homeMarker, - forkMarker, - flagMarker, - 32, - 32, - -6, - -8, - data, - ); + const marker = createPin(homeMarker, forkMarker, flagMarker, 32, 32, -6, -8, data); clickMarker(marker, i); marker.setMap(map); currentMarkers.push(marker); @@ -110,16 +89,7 @@ function Map({ data, categoryChange }: PropsType) { const currentMarkers: any[] = []; data.map((data) => { - const marker = createPin( - bigHomeMarker, - bigForkMarker, - bigFlagMarker, - 44, - 52, - 0, - 0, - data, - ); + const marker = createPin(bigHomeMarker, bigForkMarker, bigFlagMarker, 44, 52, 0, 0, data); marker.setZIndex(10); currentMarkers.push(marker); }); @@ -136,7 +106,7 @@ function Map({ data, categoryChange }: PropsType) { // 컴포넌트 마운트, 카테고리 전환 시 새로운 맵 생성 useEffect(() => { - const container = document.getElementById("map"); + const container = document.getElementById('map'); setPin([]); // React.StrictMode로 인해 map이 두 번 중첩되어 겹치는 현상 방지 @@ -160,12 +130,7 @@ function Map({ data, categoryChange }: PropsType) { setSmallPin(data); setBigPin(data); data.map((data) => { - bounds.extend( - new window.kakao.maps.LatLng( - data.location.latitude, - data.location.longtitude, - ), - ); + bounds.extend(new window.kakao.maps.LatLng(data.location.latitude, data.location.longtitude)); }); map.setBounds(bounds); } @@ -181,7 +146,7 @@ function Map({ data, categoryChange }: PropsType) { return (
    -
    +
    >; + keywordClick: boolean; } -function SearchList({forSearch, setForSearch}: PropsType) { +function SearchList({forSearch, keywordClick}: PropsType) { const [data, setData] = useState(); const [filterData, setFilterData] = useState(); const [categoryChange, setCategoryChange] = useState(false); @@ -27,24 +27,17 @@ function SearchList({forSearch, setForSearch}: PropsType) { const [searchParams] = useSearchParams(); useEffect(() => { - getData('home/search/search', setData); - - const querystring = searchParams.get('category'); - const beforeData = forSearch; - if (querystring) { - beforeData.category = querystring; - setForSearch(beforeData); - } + getData('api/home/search/search', setData); }, [searchParams]); useEffect(() => { if (data) { - if (forSearch.category !== '전체') { + if (forSearch.category !== 0) { let filterData; - if (forSearch.category === '맛집') { - filterData = data.filter((data) => data.category === '음식점' || data.category === '카페'); + if (forSearch.category === 14) { + filterData = data.filter((data) => data.contentTypeId === 14 || data.contentTypeId === 15); } else { - filterData = data.filter((data) => data.category === forSearch.category); + filterData = data.filter((data) => data.contentTypeId === forSearch.category); } setFilterData(filterData); } else { @@ -54,9 +47,6 @@ function SearchList({forSearch, setForSearch}: PropsType) { }, [data, forSearch.category]); function onMap() { - const beforeData = forSearch; - beforeData.map = 'true'; - setForSearch(beforeData); navigate( `/home/search?keyword=${forSearch.keyword}&category=${forSearch.category}&map=true&location=${forSearch.location}&sort=${forSearch.sort}`, ); @@ -64,13 +54,13 @@ function SearchList({forSearch, setForSearch}: PropsType) { return (
    - + {forSearch.map === 'true' && filterData ? ( ) : ( <>
    - +
      diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx index 78bc479a..f0fdbbdf 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx @@ -2,26 +2,24 @@ import {useNavigate} from 'react-router-dom'; import styles from './Tab.module.scss'; +import {translateCategoryToNum, translateCategoryToStr} from '@/hooks/Search/useSearch'; + import {ForSearchType} from '@/types/home'; interface PropsType { forSearch: ForSearchType; thisCategory: string; setCategoryChange: React.Dispatch>; - setForSearch: React.Dispatch>; } -function Tab({forSearch, thisCategory, setForSearch, setCategoryChange}: PropsType) { +function Tab({forSearch, thisCategory, setCategoryChange}: PropsType) { const navigate = useNavigate(); - function handleCategory(key: string) { + function handleCategory(key: number) { setCategoryChange(true); setTimeout(() => { setCategoryChange(false); }, 150); - const beforeData = forSearch; - beforeData.category = key; - setForSearch(beforeData); navigate( `/home/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}`, ); @@ -32,11 +30,11 @@ function Tab({forSearch, thisCategory, setForSearch, setCategoryChange}: PropsTy className={styles.container} id={thisCategory} style={{ - color: forSearch.category === thisCategory ? '#1d2433' : '#cdcfd0', - borderBottom: forSearch.category === thisCategory ? '2px solid #1d2433' : 'none', + color: translateCategoryToStr(forSearch.category) === thisCategory ? '#1d2433' : '#cdcfd0', + borderBottom: translateCategoryToStr(forSearch.category) === thisCategory ? '2px solid #1d2433' : 'none', }} onClick={() => { - handleCategory(thisCategory); + handleCategory(translateCategoryToNum(thisCategory)); }} > {thisCategory} diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx index 40a5bf70..2fc5748c 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx @@ -12,11 +12,10 @@ import {ForSearchType} from '@/types/home'; interface PropsType { forSearch: ForSearchType; - setForSearch: React.Dispatch>; setCategoryChange: React.Dispatch>; } -function Tabs({forSearch, setForSearch, setCategoryChange}: PropsType) { +function Tabs({forSearch, setCategoryChange}: PropsType) { const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); const thisCategory = ['전체', '맛집', '숙소', '관광지', '문화시설', '레포츠', '쇼핑']; @@ -43,7 +42,6 @@ function Tabs({forSearch, setForSearch, setCategoryChange}: PropsType) { {thisCategory.map((thisCategory) => ( { + http.get('api/home/vote', () => { return HttpResponse.json(userVoteData, { status: 200, }); }), - http.get('/api/home/tripSpace', () => { + http.get('api/home/tripSpace', () => { return HttpResponse.json(tripSpaceData, { status: 200, }); }), // 홈 검색 - http.get('/api/home/search/keyword', () => { + http.get('api/places/popular/keywords', () => { return HttpResponse.json(searchKeywordData, { status: 200, }); }), - http.get('/api/places/popular', () => { + http.get('api/places/popular', () => { return HttpResponse.json(hotPlaces, { status: 200, }); }), - http.get('/api/home/search/hothotel', () => { - return HttpResponse.json(hotHotels, { - status: 200, - }); - }), - http.get('/api/home/search/search', () => { + http.get('api/home/search/search', () => { return HttpResponse.json(searchItemData, { status: 200, }); @@ -608,7 +511,7 @@ export const home = [ // 추후 api폴더가 생기면 함수를 옮기겠습니다. export async function getData(apiURL: string, set: Dispatch>) { try { - const fetchData = await axios.get(`/api/${apiURL}`); + const fetchData = await axios.get(`${apiURL}`); set(fetchData.data); } catch (error) { console.log(error); diff --git a/src/pages/SearchFromHome/SearchFromHome.tsx b/src/pages/SearchFromHome/SearchFromHome.tsx index ceef949b..3c93a13b 100644 --- a/src/pages/SearchFromHome/SearchFromHome.tsx +++ b/src/pages/SearchFromHome/SearchFromHome.tsx @@ -13,12 +13,13 @@ import SearchList from '@/components/SearchFromHome/SearchList/SearchList'; function SearchFromHome() { const [forSearch, setForSearch] = useState({ keyword: '', - category: '전체', + category: 0, map: 'false', location: '전국', sort: '등록순', }); const [searchParams] = useSearchParams(); + const [kewordClick, setKeywordClick] = useState(false); const vh = window.innerHeight; useEffect(() => { @@ -33,7 +34,7 @@ function SearchFromHome() { if (querystring.keyword && querystring.category && querystring.location && querystring.map && querystring.sort) { setForSearch({ keyword: querystring.keyword, - category: querystring.category, + category: parseInt(querystring.category), map: querystring.map, location: querystring.location, sort: querystring.sort, @@ -57,9 +58,9 @@ function SearchFromHome() { )} {forSearch.keyword === '' ? ( - + ) : ( - + )}
    ); diff --git a/src/types/home.ts b/src/types/home.ts index 8126bba2..5fae2043 100644 --- a/src/types/home.ts +++ b/src/types/home.ts @@ -82,7 +82,7 @@ export interface SearchItemType { export interface ForSearchType { keyword: string; - category: string; + category: number; map: string; location: string; sort: string; diff --git a/src/utils/categories.json b/src/utils/categories.json new file mode 100644 index 00000000..5e42b55d --- /dev/null +++ b/src/utils/categories.json @@ -0,0 +1,610 @@ +[ + { + "code": "A01010100", + "name": "국립공원" + }, + { + "code": "A01010200", + "name": "도립공원" + }, + { + "code": "A01010300", + "name": "군립공원" + }, + { + "code": "A01010400", + "name": "산" + }, + { + "code": "A01010500", + "name": "자연생태관광지" + }, + { + "code": "A01010600", + "name": "자연휴양림" + }, + { + "code": "A01010700", + "name": "수목원" + }, + { + "code": "A01010800", + "name": "폭포" + }, + { + "code": "A01010900", + "name": "계곡" + }, + { + "code": "A01011000", + "name": "약수터" + }, + { + "code": "A01011100", + "name": "해안절경" + }, + { + "code": "A01011200", + "name": "해수욕장" + }, + { + "code": "A01011300", + "name": "섬" + }, + { + "code": "A01011400", + "name": "항구/포구" + }, + { + "code": "A01011600", + "name": "등대" + }, + { + "code": "A01011700", + "name": "호수" + }, + { + "code": "A01011800", + "name": "강" + }, + { + "code": "A01011900", + "name": "동굴" + }, + { + "code": "A01020100", + "name": "희귀동.식물" + }, + { + "code": "A01020200", + "name": "기암괴석" + }, + { + "code": "A02010100", + "name": "고궁" + }, + { + "code": "A02010200", + "name": "성" + }, + { + "code": "A02010300", + "name": "문" + }, + { + "code": "A02010400", + "name": "고택" + }, + { + "code": "A02010500", + "name": "생가" + }, + { + "code": "A02010600", + "name": "민속마을" + }, + { + "code": "A02010700", + "name": "유적지/사적지" + }, + { + "code": "A02010800", + "name": "사찰" + }, + { + "code": "A02010900", + "name": "종교성지" + }, + { + "code": "A02011000", + "name": "안보관광" + }, + { + "code": "A02020200", + "name": "관광단지" + }, + { + "code": "A02020300", + "name": "온천/욕장/스파" + }, + { + "code": "A02020400", + "name": "이색찜질방" + }, + { + "code": "A02020500", + "name": "헬스투어" + }, + { + "code": "A02020600", + "name": "테마공원" + }, + { + "code": "A02020700", + "name": "공원" + }, + { + "code": "A02020800", + "name": "유람선/잠수함관광" + }, + { + "code": "A02030100", + "name": "농.산.어촌 체험" + }, + { + "code": "A02030200", + "name": "전통체험" + }, + { + "code": "A02030300", + "name": "산사체험" + }, + { + "code": "A02030400", + "name": "이색체험" + }, + { + "code": "A02030600", + "name": "이색거리" + }, + { + "code": "A02040400", + "name": "발전소" + }, + { + "code": "A02040600", + "name": "식음료" + }, + { + "code": "A02040800", + "name": "기타" + }, + { + "code": "A02040900", + "name": "전자-반도체" + }, + { + "code": "A02041000", + "name": "자동차" + }, + { + "code": "A02050100", + "name": "다리/대교" + }, + { + "code": "A02050200", + "name": "기념탑/기념비/전망대" + }, + { + "code": "A02050300", + "name": "분수" + }, + { + "code": "A02050400", + "name": "동상" + }, + { + "code": "A02050500", + "name": "터널" + }, + { + "code": "A02050600", + "name": "유명건물" + }, + { + "code": "A02060100", + "name": "박물관" + }, + { + "code": "A02060200", + "name": "기념관" + }, + { + "code": "A02060300", + "name": "전시관" + }, + { + "code": "A02060400", + "name": "컨벤션센터" + }, + { + "code": "A02060500", + "name": "미술관/화랑" + }, + { + "code": "A02060600", + "name": "공연장" + }, + { + "code": "A02060700", + "name": "문화원" + }, + { + "code": "A02060800", + "name": "외국문화원" + }, + { + "code": "A02060900", + "name": "도서관" + }, + { + "code": "A02061000", + "name": "대형서점" + }, + { + "code": "A02061100", + "name": "문화전수시설" + }, + { + "code": "A02061200", + "name": "영화관" + }, + { + "code": "A02061300", + "name": "어학당" + }, + { + "code": "A02061400", + "name": "학교" + }, + { + "code": "A02070100", + "name": "문화관광축제" + }, + { + "code": "A02070200", + "name": "일반축제" + }, + { + "code": "A02080100", + "name": "전통공연" + }, + { + "code": "A02080200", + "name": "연극" + }, + { + "code": "A02080300", + "name": "뮤지컬" + }, + { + "code": "A02080400", + "name": "오페라" + }, + { + "code": "A02080500", + "name": "전시회" + }, + { + "code": "A02080600", + "name": "박람회" + }, + { + "code": "A02080800", + "name": "무용" + }, + { + "code": "A02080900", + "name": "클래식음악회" + }, + { + "code": "A02081000", + "name": "대중콘서트" + }, + { + "code": "A02081100", + "name": "영화" + }, + { + "code": "A02081200", + "name": "스포츠경기" + }, + { + "code": "A02081300", + "name": "기타행사" + }, + { + "code": "C01120001", + "name": "가족코스" + }, + { + "code": "C01130001", + "name": "나홀로코스" + }, + { + "code": "C01140001", + "name": "힐링코스" + }, + { + "code": "C01150001", + "name": "도보코스" + }, + { + "code": "C01160001", + "name": "캠핑코스" + }, + { + "code": "C01170001", + "name": "맛코스" + }, + { + "code": "A03010200", + "name": "수상레포츠" + }, + { + "code": "A03010300", + "name": "항공레포츠" + }, + { + "code": "A03020200", + "name": "수련시설" + }, + { + "code": "A03020300", + "name": "경기장" + }, + { + "code": "A03020400", + "name": "인라인(실내 인라인 포함)" + }, + { + "code": "A03020500", + "name": "자전거하이킹" + }, + { + "code": "A03020600", + "name": "카트" + }, + { + "code": "A03020700", + "name": "골프" + }, + { + "code": "A03020800", + "name": "경마" + }, + { + "code": "A03020900", + "name": "경륜" + }, + { + "code": "A03021000", + "name": "카지노" + }, + { + "code": "A03021100", + "name": "승마" + }, + { + "code": "A03021200", + "name": "스키/스노보드" + }, + { + "code": "A03021300", + "name": "스케이트" + }, + { + "code": "A03021400", + "name": "썰매장" + }, + { + "code": "A03021500", + "name": "수렵장" + }, + { + "code": "A03021600", + "name": "사격장" + }, + { + "code": "A03021700", + "name": "야영장,오토캠핑장" + }, + { + "code": "A03021800", + "name": "암벽등반" + }, + { + "code": "A03022000", + "name": "서바이벌게임" + }, + { + "code": "A03022100", + "name": "ATV" + }, + { + "code": "A03022200", + "name": "MTB" + }, + { + "code": "A03022300", + "name": "오프로드" + }, + { + "code": "A03022400", + "name": "번지점프" + }, + { + "code": "A03022600", + "name": "스키(보드) 렌탈샵" + }, + { + "code": "A03022700", + "name": "트래킹" + }, + { + "code": "A03030100", + "name": "윈드서핑/제트스키" + }, + { + "code": "A03030200", + "name": "카약/카누" + }, + { + "code": "A03030300", + "name": "요트" + }, + { + "code": "A03030400", + "name": "스노쿨링/스킨스쿠버다이빙" + }, + { + "code": "A03030500", + "name": "민물낚시" + }, + { + "code": "A03030600", + "name": "바다낚시" + }, + { + "code": "A03030700", + "name": "수영" + }, + { + "code": "A03030800", + "name": "래프팅" + }, + { + "code": "A03040100", + "name": "스카이다이빙" + }, + { + "code": "A03040200", + "name": "초경량비행" + }, + { + "code": "A03040300", + "name": "헹글라이딩/패러글라이딩" + }, + { + "code": "A03040400", + "name": "열기구" + }, + { + "code": "A03050100", + "name": "복합 레포츠" + }, + { + "code": "B02010100", + "name": "관광호텔" + }, + { + "code": "B02010500", + "name": "콘도미니엄" + }, + { + "code": "B02010600", + "name": "유스호스텔" + }, + { + "code": "B02010700", + "name": "펜션" + }, + { + "code": "B02010900", + "name": "모텔" + }, + { + "code": "B02011000", + "name": "민박" + }, + { + "code": "B02011100", + "name": "게스트하우스" + }, + { + "code": "B02011200", + "name": "홈스테이" + }, + { + "code": "B02011300", + "name": "서비스드레지던스" + }, + { + "code": "B02011600", + "name": "한옥" + }, + { + "code": "A04010100", + "name": "5일장" + }, + { + "code": "A04010200", + "name": "상설시장" + }, + { + "code": "A04010300", + "name": "백화점" + }, + { + "code": "A04010400", + "name": "면세점" + }, + { + "code": "A04010500", + "name": "대형마트" + }, + { + "code": "A04010600", + "name": "전문매장/상가" + }, + { + "code": "A04010700", + "name": "공예/공방" + }, + { + "code": "A04010900", + "name": "특산물판매점" + }, + { + "code": "A04011000", + "name": "사후면세점" + }, + { + "code": "A05020100", + "name": "한식" + }, + { + "code": "A05020200", + "name": "서양식" + }, + { + "code": "A05020300", + "name": "일식" + }, + { + "code": "A05020400", + "name": "중식" + }, + { + "code": "A05020700", + "name": "이색음식점" + }, + { + "code": "A05020900", + "name": "카페/전통찻집" + }, + { + "code": "A05021000", + "name": "클럽" + } +] \ No newline at end of file diff --git a/src/utils/contentType.json b/src/utils/contentType.json new file mode 100644 index 00000000..11fdae59 --- /dev/null +++ b/src/utils/contentType.json @@ -0,0 +1,30 @@ +[ + { + "name": "맛집", + "id": 39 + }, + { + "name": "관광지", + "id": 12 + }, + { + "name": "문화시설", + "id": 14 + }, + { + "name": "문화시설", + "id": 15 + }, + { + "name": "레포츠", + "id": 28 + }, + { + "name": "숙소", + "id": 32 + }, + { + "name": "쇼핑", + "id": 38 + } +] From 84ab68c8fa89d544ced88097f56f0e9e0bffb136 Mon Sep 17 00:00:00 2001 From: Yamyam-code Date: Mon, 22 Jan 2024 04:57:46 +0900 Subject: [PATCH 39/50] Fix: build error --- src/components/SearchFromHome/SearchList/SearchList.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/SearchFromHome/SearchList/SearchList.tsx b/src/components/SearchFromHome/SearchList/SearchList.tsx index 4e27cc8e..b27d2703 100644 --- a/src/components/SearchFromHome/SearchList/SearchList.tsx +++ b/src/components/SearchFromHome/SearchList/SearchList.tsx @@ -27,7 +27,11 @@ function SearchList({forSearch, keywordClick}: PropsType) { const [searchParams] = useSearchParams(); useEffect(() => { - getData('api/home/search/search', setData); + if (!keywordClick) { + getData('api/home/search/search', setData); + } else { + getData('api/home/search/search', setData); + } }, [searchParams]); useEffect(() => { From f1d405d717c274dbbde9b09395e7dfde9c4a5a8c Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 05:20:40 +0900 Subject: [PATCH 40/50] Feat: add Withdrawal page --- src/assets/icons/crying_imoji.svg | 9 ++++ .../Auth/Withdrawal/Withdrawal.module.scss | 47 ++++++++++++++++++ src/pages/Auth/Withdrawal/Withdrawal.tsx | 48 +++++++++++++++++++ src/pages/User/UserPrivacy/UserPrivacy.tsx | 2 +- src/routes/MainRouter/MainRouter.tsx | 2 + 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/assets/icons/crying_imoji.svg create mode 100644 src/pages/Auth/Withdrawal/Withdrawal.module.scss create mode 100644 src/pages/Auth/Withdrawal/Withdrawal.tsx diff --git a/src/assets/icons/crying_imoji.svg b/src/assets/icons/crying_imoji.svg new file mode 100644 index 00000000..1f81bbca --- /dev/null +++ b/src/assets/icons/crying_imoji.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/pages/Auth/Withdrawal/Withdrawal.module.scss b/src/pages/Auth/Withdrawal/Withdrawal.module.scss new file mode 100644 index 00000000..3437561d --- /dev/null +++ b/src/pages/Auth/Withdrawal/Withdrawal.module.scss @@ -0,0 +1,47 @@ +@use "@/sass" as *; + +.container { + color: $neutral900; + + .body { + padding: 80px 20px; + + h2 { + @include typography(headline); + + .imoji { + display: inline-block; + width: 24px; + height: 24px; + margin-left: 4px; + vertical-align: text-top; + + background-image: url("@/assets/icons/crying_imoji.svg"); + background-position: center; + background-repeat: no-repeat; + } + } + + .bar { + width: 100%; + height: 1px; + margin: 16px 0; + background-color: $neutral200; + } + + .desc { + margin-top: 24px; + + @include typography(tabLabel); + } + + .removeList { + margin-bottom: 16px; + @include typography(captionSmall); + + & > li { + margin-bottom: 8px; + } + } + } +} diff --git a/src/pages/Auth/Withdrawal/Withdrawal.tsx b/src/pages/Auth/Withdrawal/Withdrawal.tsx new file mode 100644 index 00000000..38585db5 --- /dev/null +++ b/src/pages/Auth/Withdrawal/Withdrawal.tsx @@ -0,0 +1,48 @@ +import styles from "./Withdrawal.module.scss"; + +import AuthButton from "@/components/Auth/Button/AuthButton"; +import Header from "@/components/Auth/Header/Header"; + +function Withdrawal() { + const onClickButton = () => { + // 회원탈퇴 api + }; + + return ( +
    +
    + +
    +

    + 트립보트를 떠나신다니 +
    + 아쉬워요 + +

    + +
    +

    탈퇴시 삭제되는 정보를 확인하세요.

    +

    한번 삭제된 정보는 복구가 불가능합니다.

    +
    + +
    + +
      +
    • 1. 계정 및 프로필 정보
    • +
    • 2. 내 여행 스페이스 투표 및 일정
    • +
    • 3. 저장된 찜 목록 관련 정보
    • +
    • 4. 내가 작성한 리뷰
    • +
    + + +
    +
    + ); +} + +export default Withdrawal; diff --git a/src/pages/User/UserPrivacy/UserPrivacy.tsx b/src/pages/User/UserPrivacy/UserPrivacy.tsx index b73c68ed..5504218f 100644 --- a/src/pages/User/UserPrivacy/UserPrivacy.tsx +++ b/src/pages/User/UserPrivacy/UserPrivacy.tsx @@ -33,7 +33,7 @@ function UserPrivacy() {
  • - 회원 탈퇴 + 회원 탈퇴
diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index b9b17def..239b2aaa 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -7,6 +7,7 @@ import ModifyPassword from "@/pages/Auth/ModifyPassword/ModifyPassword"; import AgreePrivacy from "@/pages/Auth/Signup/Agree/AgreePrivacy"; import AgreeService from "@/pages/Auth/Signup/Agree/AgreeService"; import Signup from "@/pages/Auth/Signup/Signup"; +import Withdrawal from "@/pages/Auth/Withdrawal/Withdrawal"; import Calendar from "@/pages/Calendar/Calendar"; import Detail from "@/pages/Detail/Detail"; import Home from "@/pages/Home/Home"; @@ -42,6 +43,7 @@ function MainRouter() { } /> } /> } /> + } /> } /> } /> } /> From 17ebefd490d4e017c0e003a8bf907f3393d33321 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 06:51:21 +0900 Subject: [PATCH 41/50] Feat: add bottom slide in myreview page --- src/assets/icons/pencil_with_line.svg | 5 +++ src/assets/icons/trash_icon.svg | 5 +++ .../ActionList/ActionList.module.scss | 33 +++++++++++++++++ .../MyReview/ActionList/ActionList.tsx | 35 ++++++++++++++++++ src/pages/User/MyReview/MyReview.module.scss | 10 ++++- src/pages/User/MyReview/MyReview.tsx | 37 ++++++++++++++++--- src/pages/User/User.tsx | 1 - src/types/myReview.ts | 5 +++ 8 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 src/assets/icons/pencil_with_line.svg create mode 100644 src/assets/icons/trash_icon.svg create mode 100644 src/components/MyReview/ActionList/ActionList.module.scss create mode 100644 src/components/MyReview/ActionList/ActionList.tsx create mode 100644 src/types/myReview.ts diff --git a/src/assets/icons/pencil_with_line.svg b/src/assets/icons/pencil_with_line.svg new file mode 100644 index 00000000..d31ae5be --- /dev/null +++ b/src/assets/icons/pencil_with_line.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/trash_icon.svg b/src/assets/icons/trash_icon.svg new file mode 100644 index 00000000..3a70928f --- /dev/null +++ b/src/assets/icons/trash_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/MyReview/ActionList/ActionList.module.scss b/src/components/MyReview/ActionList/ActionList.module.scss new file mode 100644 index 00000000..e2338f96 --- /dev/null +++ b/src/components/MyReview/ActionList/ActionList.module.scss @@ -0,0 +1,33 @@ +@use "@/sass" as *; + +.container { + display: flex; + flex-direction: column; + gap: 20px; + + & > li { + display: flex; + align-items: center; + gap: 16px; + cursor: pointer; + + .editIcon { + display: flex; + justify-content: center; + align-items: center; + width: 2.4rem; + height: 2.4rem; + + background: $neutral100; + border-radius: 50%; + } + + & > p { + @include typography(subTitle); + } + + &:hover p { + font-weight: 700; + } + } +} diff --git a/src/components/MyReview/ActionList/ActionList.tsx b/src/components/MyReview/ActionList/ActionList.tsx new file mode 100644 index 00000000..77bd3afb --- /dev/null +++ b/src/components/MyReview/ActionList/ActionList.tsx @@ -0,0 +1,35 @@ +import styles from "./ActionList.module.scss"; + +import EditIcon from "@/assets/icons/pencil_with_line.svg?react"; +import TrashIcon from "@/assets/icons/trash_icon.svg?react"; + +import { ActionListProps } from "@/types/myReview"; + +function ActionList({ reviewId }: ActionListProps) { + return ( +
    +
  • { + console.log(reviewId); + }} + > +
    + +
    +

    수정하기

    +
  • +
  • { + console.log(reviewId); + }} + > +
    + +
    +

    삭제하기

    +
  • +
+ ); +} + +export default ActionList; diff --git a/src/pages/User/MyReview/MyReview.module.scss b/src/pages/User/MyReview/MyReview.module.scss index 56043263..c5b8b94f 100644 --- a/src/pages/User/MyReview/MyReview.module.scss +++ b/src/pages/User/MyReview/MyReview.module.scss @@ -14,11 +14,17 @@ &__header { display: flex; - gap: 12px; padding: 0 20px; + justify-content: space-between; + + & > div { + display: flex; + gap: 12px; + } &__img { width: 4rem; + height: 4rem; border-radius: 8px; background-position: center; @@ -36,7 +42,7 @@ } &__meatball { - flex: 1; + // flex: 1; display: flex; justify-content: end; diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx index 66b2c6e8..cf50ada8 100644 --- a/src/pages/User/MyReview/MyReview.tsx +++ b/src/pages/User/MyReview/MyReview.tsx @@ -1,14 +1,19 @@ +import { useDisclosure } from "@chakra-ui/react"; +import { useRef } from "react"; + import styles from "./MyReview.module.scss"; import Header from "@/components/Auth/Header/Header"; +import BottomSlide from "@/components/BottomSlide/BottomSlide"; import ReviewImageSlider from "@/components/Detail/Contents/Review/ReviewImageSlider/ReviewImageSlider"; +import ActionList from "@/components/MyReview/ActionList/ActionList"; import Meatball from "@/assets/icons/meatball.svg?react"; import Star from "@/assets/icons/star_fill.svg?react"; const data = [ { - id: "123", + id: "123a", place: { area: "대전", category: "맛집 · 서울", @@ -31,7 +36,7 @@ const data = [ ], }, { - id: "123", + id: "123sd", place: { area: "대전", category: "맛집 · 서울", @@ -52,7 +57,7 @@ const data = [ ], }, { - id: "123", + id: "123asaa", place: { area: "대전", category: "맛집 · 서울", @@ -73,7 +78,7 @@ const data = [ ], }, { - id: "123", + id: "1231qa", place: { area: "대전", category: "맛집 · 서울", @@ -94,7 +99,7 @@ const data = [ ], }, { - id: "123", + id: "123xcv", place: { area: "대전", category: "맛집 · 서울", @@ -117,6 +122,14 @@ const data = [ ]; function MyReview() { + const { + isOpen: isBottomSlideOpen, + onOpen: onBottomSlideOpen, + onClose: onBottomSlideClose, + } = useDisclosure(); + + const clickedReviewId = useRef(""); + return (
@@ -139,7 +152,13 @@ function MyReview() {
-
@@ -159,6 +178,12 @@ function MyReview() { ))} + + } + />
); } diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index cf5c2d21..878ec283 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -26,7 +26,6 @@ function User() { const onClickAlert = () => { if (Notification.permission === "denied") { - //브라우저 알림 설정이 해제되어있습니다 alert return; } diff --git a/src/types/myReview.ts b/src/types/myReview.ts new file mode 100644 index 00000000..0c2709df --- /dev/null +++ b/src/types/myReview.ts @@ -0,0 +1,5 @@ +import { MutableRefObject } from "react"; + +export interface ActionListProps { + reviewId: MutableRefObject; +} From 42427e3cb2ef814b41f2d466bc24183f93f539cc Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 13:08:57 +0900 Subject: [PATCH 42/50] Hotfix: fix myreview's layout --- src/pages/User/MyReview/MyReview.tsx | 43 ++++++++++++++++------------ src/types/myReview.ts | 2 +- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx index cf50ada8..0f826df8 100644 --- a/src/pages/User/MyReview/MyReview.tsx +++ b/src/pages/User/MyReview/MyReview.tsx @@ -13,8 +13,9 @@ import Star from "@/assets/icons/star_fill.svg?react"; const data = [ { - id: "123a", + id: 1231, place: { + id: 41414, area: "대전", category: "맛집 · 서울", title: "대전 성심당", @@ -36,8 +37,9 @@ const data = [ ], }, { - id: "123sd", + id: 12555, place: { + id: 42311, area: "대전", category: "맛집 · 서울", title: "대전 성심당", @@ -57,8 +59,9 @@ const data = [ ], }, { - id: "123asaa", + id: 1111, place: { + id: 535334, area: "대전", category: "맛집 · 서울", title: "대전 성심당", @@ -78,8 +81,9 @@ const data = [ ], }, { - id: "1231qa", + id: 221411, place: { + id: 655454, area: "대전", category: "맛집 · 서울", title: "대전 성심당", @@ -99,8 +103,9 @@ const data = [ ], }, { - id: "123xcv", + id: 221331, place: { + id: 645445, area: "대전", category: "맛집 · 서울", title: "대전 성심당", @@ -128,7 +133,7 @@ function MyReview() { onClose: onBottomSlideClose, } = useDisclosure(); - const clickedReviewId = useRef(""); + const clickedReviewId = useRef(); return (
@@ -138,24 +143,26 @@ function MyReview() { {data.map(({ id, place, visitedAt, rating, content, images }) => (
  • -
    - -
    -
    - {place.title} -
    -
    - {place.category} +
    +
    + +
    +
    + {place.title} +
    +
    + {place.category} +
    -
    {`${startDate} ~ ${endDate}`}
    +
    + {setMyspaceDate.upcomming(startDate, endDate)} +
  • ))} diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts new file mode 100644 index 00000000..7a70bcca --- /dev/null +++ b/src/utils/formatDate.ts @@ -0,0 +1,27 @@ +import { format } from "date-fns"; + +const createDate = (date: string) => { + const arrayForm = date.split("-").map((el) => Number(el)); + + return new Date(arrayForm[0], arrayForm[1] - 1, arrayForm[2]); +}; + +export const setMyspaceDate = { + upcomming: (start: string, end: string) => { + const startFormat = format(createDate(start), "yyyy.MM.dd"); + const endFormat = format(createDate(end), "yyyy.MM.dd"); + + if (end) { + return `${startFormat} - ${endFormat}`; + } else { + return startFormat; + } + }, + + outdated: (start: string, end: string) => {}, +}; + +export const setMyReviewDate = (visitedAt: string) => { + const visitFormat = format(createDate(visitedAt), "yyyy'년' M'월'"); + return visitFormat; +}; From 4e3715eb6e21be1dd0cb7a83239e4206cfe71263 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 15:29:04 +0900 Subject: [PATCH 44/50] Refactor: way to export setMyspaceDate obj -> func --- src/pages/User/MySpace/MySpace.tsx | 34 ++++++++++++++++++------------ src/utils/formatDate.ts | 20 +++++++----------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/pages/User/MySpace/MySpace.tsx b/src/pages/User/MySpace/MySpace.tsx index c54351ba..8ef1b480 100644 --- a/src/pages/User/MySpace/MySpace.tsx +++ b/src/pages/User/MySpace/MySpace.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { useNavigate } from "react-router-dom"; import styles from "./MySpace.module.scss"; @@ -8,56 +9,56 @@ import { setMyspaceDate } from "@/utils/formatDate"; const upcomming = [ { - id: "asdfasdf", + id: 12312, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-12", endDate: "2024-01-14", dueDate: 0, }, { - id: "asdfasds1", + id: 12321, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 4, }, { - id: "asdf123as", + id: 12334, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 15, }, { - id: "asdd12ddddd", + id: 12343, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 15, }, { - id: "asd123d1d11d", + id: 12352, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 15, }, { - id: "1234asdaaa", + id: 12315, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 15, }, { - id: "1234asda12", + id: 123253, title: "강릉, 속초, 전주, 여수 여행", startDate: "2024-01-18", endDate: "2024-01-22", dueDate: 15, }, { - id: "1234asdazz", + id: 1231617, title: "강릉, 속초, 전주, 여수 여행", startDate: "2025-01-18", endDate: "2025-01-22", @@ -67,21 +68,21 @@ const upcomming = [ const outdated = [ { - id: "asdfasdf1", + id: 812367, title: "강릉, 속초, 전주, 여수 여행", startDate: "2023-12-17", endDate: "2023-12-21", dueDate: 0, }, { - id: "asdfasds124", + id: 867123, title: "캐리비안베이, 로만바스 온천, 라마드 호텔, 에버랜드", startDate: "2023-04-17", endDate: "2023-04-20", dueDate: 0, }, { - id: "asdf123as15", + id: 67123, title: "강릉, 속초, 전주, 여수 여행", startDate: "2022-12-29", endDate: "2022-12-30", @@ -96,6 +97,8 @@ function MySpace() { const [tab, setTab] = useState("upcomming"); const [data, setData] = useState(upcomming); + const navigate = useNavigate(); + return (
    @@ -134,7 +137,12 @@ function MySpace() {
      {data.map(({ id, title, startDate, endDate, dueDate }) => ( -
    • +
    • { + // navigate(`/trip/${id}`) + }} + >
      - {setMyspaceDate.upcomming(startDate, endDate)} + {setMyspaceDate(startDate, endDate)}
    • diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts index 7a70bcca..ede0f6c3 100644 --- a/src/utils/formatDate.ts +++ b/src/utils/formatDate.ts @@ -6,19 +6,15 @@ const createDate = (date: string) => { return new Date(arrayForm[0], arrayForm[1] - 1, arrayForm[2]); }; -export const setMyspaceDate = { - upcomming: (start: string, end: string) => { - const startFormat = format(createDate(start), "yyyy.MM.dd"); - const endFormat = format(createDate(end), "yyyy.MM.dd"); +export const setMyspaceDate = (start: string, end: string) => { + const startFormat = format(createDate(start), "yyyy.MM.dd"); + const endFormat = format(createDate(end), "yyyy.MM.dd"); - if (end) { - return `${startFormat} - ${endFormat}`; - } else { - return startFormat; - } - }, - - outdated: (start: string, end: string) => {}, + if (end) { + return `${startFormat} - ${endFormat}`; + } else { + return startFormat; + } }; export const setMyReviewDate = (visitedAt: string) => { From 574744d24b04e7216c9af37b6bd896a2ac56cb46 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 15:40:13 +0900 Subject: [PATCH 45/50] Hotfix: change navigate path findpassword --- src/pages/Auth/Login/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Auth/Login/Login.tsx b/src/pages/Auth/Login/Login.tsx index 590c25cf..267af92e 100644 --- a/src/pages/Auth/Login/Login.tsx +++ b/src/pages/Auth/Login/Login.tsx @@ -31,7 +31,7 @@ function Login() {
      - 비밀번호를 잊으셨나요? + 비밀번호를 잊으셨나요?
      회원가입
      From 81b2e7a6fadf2d7883923c1d4c7ded7d4754ab4f Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 16:09:08 +0900 Subject: [PATCH 46/50] Refactor: seperate components from User page --- .../User/MypageList/MypageList.module.scss | 120 +++++++++++ src/components/User/MypageList/MypageList.tsx | 77 +++++++ src/components/User/Mywork/Mywork.module.scss | 40 ++++ src/components/User/Mywork/Mywork.tsx | 36 ++++ .../User/Profile/Profile.module.scss | 39 ++++ src/components/User/Profile/Profile.tsx | 33 +++ src/pages/User/User.module.scss | 194 ------------------ src/pages/User/User.tsx | 118 +---------- src/pages/User/UserPrivacy/UserPrivacy.tsx | 3 + src/types/user.ts | 10 + 10 files changed, 366 insertions(+), 304 deletions(-) create mode 100644 src/components/User/MypageList/MypageList.module.scss create mode 100644 src/components/User/MypageList/MypageList.tsx create mode 100644 src/components/User/Mywork/Mywork.module.scss create mode 100644 src/components/User/Mywork/Mywork.tsx create mode 100644 src/components/User/Profile/Profile.module.scss create mode 100644 src/components/User/Profile/Profile.tsx create mode 100644 src/types/user.ts diff --git a/src/components/User/MypageList/MypageList.module.scss b/src/components/User/MypageList/MypageList.module.scss new file mode 100644 index 00000000..cfe7c895 --- /dev/null +++ b/src/components/User/MypageList/MypageList.module.scss @@ -0,0 +1,120 @@ +@use "@/sass" as *; + +.container { + & > li { + display: flex; + justify-content: space-between; + align-items: center; + + padding: 24px 20px; + border-bottom: 1px solid $neutral200; + cursor: pointer; + + @include typography(tabLabel); + + &:hover { + font-weight: 600; + border-color: $neutral300; + } + + svg { + fill: $neutral400; + } + } + + .alert { + &__left { + display: flex; + align-items: center; + gap: 4px; + + &__tooltip { + position: relative; + + .tooltipList { + display: none; + position: absolute; + left: -38px; + width: 20rem; + padding: 21px 8px 8px 16px; + margin-top: 2px; + border-radius: 8px; + + background-image: url("@/assets/icons/tooltipFrame.svg"); + background-repeat: no-repeat; + background-size: auto; + + list-style-type: disc; + list-style-position: inside; + + @include typography(captionSmall); + line-height: 1.8rem; + color: $neutral0; + + @include animate(showTooltip, 0.2s, ease-in, forwards); + + & > li > span { + margin-left: -5px; + } + } + + & > button { + display: block; + + &:focus ~ .tooltipList, + &:hover ~ .tooltipList { + display: block; + } + } + } + } + + &__button { + position: relative; + width: 5.6rem; + height: 3.2rem; + border-radius: 32px; + + .alertState { + position: absolute; + top: 0; + width: 3.2rem; + height: 3.2rem; + + background-color: $neutral0; + border-radius: 50%; + transition: all 0.2s ease-in; + } + + &.on { + background-color: $success300; + .alertState { + left: calc(100% - 3.2rem); + border: 2px solid $success300; + } + } + &.off { + background-color: $neutral200; + .alertState { + left: 0; + border: 2px solid $neutral200; + } + } + } + } + + .version { + color: $neutral400; + } +} + +@include keyframes(showTooltip) { + 0% { + transform: scale(0); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} diff --git a/src/components/User/MypageList/MypageList.tsx b/src/components/User/MypageList/MypageList.tsx new file mode 100644 index 00000000..7fcbc998 --- /dev/null +++ b/src/components/User/MypageList/MypageList.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import { RiArrowRightSLine } from "react-icons/ri"; +import { useNavigate } from "react-router-dom"; + +import styles from "./MypageList.module.scss"; + +import AlertIcon from "@/assets/icons/error-warning-line.svg?react"; + +function MypageList() { + const [alertOn, setAlertOn] = useState( + Notification.permission === "granted" ? true : false, + ); + const navigate = useNavigate(); + + const onClickAlert = () => { + if (Notification.permission === "denied") { + return; + } + + setAlertOn(!alertOn); + }; + + return ( +
        +
      • { + navigate("/user/privacy"); + }} + > +
        계정 관리
        + +
      • + +
      • +
        + 알림 +
        + + +
          +
        • + + 구글 크롬(Chrome) 브라우저에 최적화 되어 있어 크롬 브라우저 + 사용을 권장합니다. + +
        • +
        • + + 브라우저의 알림 설정을 켜주셔야 서비스 알림을 받을 수 + 있습니다. + +
        • +
        +
        +
        + + +
      • + +
      • +
        버전 정보
        +
        1.1.0
        +
      • +
      + ); +} + +export default MypageList; diff --git a/src/components/User/Mywork/Mywork.module.scss b/src/components/User/Mywork/Mywork.module.scss new file mode 100644 index 00000000..055963d1 --- /dev/null +++ b/src/components/User/Mywork/Mywork.module.scss @@ -0,0 +1,40 @@ +@use "@/sass" as *; + +.container { + display: flex; + align-items: center; + gap: 8px; + margin: 0 20px; + margin-bottom: 8px; + + border: 1px solid $neutral200; + border-radius: 16px; + + button { + padding: 16px; + flex: 1; + + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + + &:hover { + border-color: $primary200; + + .title { + font-weight: 600; + } + } + } + + .colBar { + width: 1px; + height: 5rem; + background-color: $neutral200; + } + + .title { + @include typography(subTitle); + } +} diff --git a/src/components/User/Mywork/Mywork.tsx b/src/components/User/Mywork/Mywork.tsx new file mode 100644 index 00000000..943d736e --- /dev/null +++ b/src/components/User/Mywork/Mywork.tsx @@ -0,0 +1,36 @@ +import { useNavigate } from "react-router-dom"; + +import styles from "./Mywork.module.scss"; + +import MapPin from "@/assets/icons/mapPin.svg?react"; +import Star from "@/assets/icons/star.svg?react"; + +function Mywork() { + const navigate = useNavigate(); + + return ( +
      + + +
      + + +
      + ); +} + +export default Mywork; diff --git a/src/components/User/Profile/Profile.module.scss b/src/components/User/Profile/Profile.module.scss new file mode 100644 index 00000000..32ea1f7b --- /dev/null +++ b/src/components/User/Profile/Profile.module.scss @@ -0,0 +1,39 @@ +@use "@/sass" as *; + +.container { +} +.container { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + + .image { + position: relative; + width: 7.2rem; + height: 7.2rem; + border-radius: 50%; + + background-position: center; + background-repeat: no-repeat; + background-size: cover; + + &__edit { + position: absolute; + bottom: 0; + right: 0; + + background-color: $neutral100; + border: 1px solid $neutral200; + border-radius: 50%; + + width: 2.6rem; + height: 2.6rem; + padding: 4px; + } + } + + .userName { + @include typography(headline); + } +} diff --git a/src/components/User/Profile/Profile.tsx b/src/components/User/Profile/Profile.tsx new file mode 100644 index 00000000..9ebd52ae --- /dev/null +++ b/src/components/User/Profile/Profile.tsx @@ -0,0 +1,33 @@ +import { useNavigate } from "react-router-dom"; + +import styles from "./Profile.module.scss"; + +import Pencil from "@/assets/icons/pencil.svg?react"; + +import { ProfileProps } from "@/types/user"; + +function Profile({ userInfo }: ProfileProps) { + const navigate = useNavigate(); + + return ( +
      +
      + +
      + +
      {userInfo.nickname}
      +
      + ); +} + +export default Profile; diff --git a/src/pages/User/User.module.scss b/src/pages/User/User.module.scss index 01ce719d..3d326d9b 100644 --- a/src/pages/User/User.module.scss +++ b/src/pages/User/User.module.scss @@ -12,198 +12,4 @@ @include typography(button); } - - .profile { - display: flex; - flex-direction: column; - align-items: center; - gap: 12px; - - &__image { - position: relative; - width: 7.2rem; - height: 7.2rem; - border-radius: 50%; - - background-position: center; - background-repeat: no-repeat; - background-size: cover; - - &__edit { - position: absolute; - bottom: 0; - right: 0; - - background-color: $neutral100; - border: 1px solid $neutral200; - border-radius: 50%; - - width: 2.6rem; - height: 2.6rem; - padding: 4px; - } - } - - &__userName { - @include typography(headline); - } - } - - .mywork { - display: flex; - align-items: center; - gap: 8px; - margin: 0 20px; - margin-bottom: 8px; - - border: 1px solid $neutral200; - border-radius: 16px; - - button { - padding: 16px; - flex: 1; - - display: flex; - flex-direction: column; - align-items: center; - gap: 6px; - - &:hover { - border-color: $primary200; - - .myworkTitle { - font-weight: 600; - } - } - } - - .colBar { - width: 1px; - height: 5rem; - background-color: $neutral200; - } - - .myworkTitle { - @include typography(subTitle); - } - } - - .infoDetail { - & > li { - display: flex; - justify-content: space-between; - align-items: center; - - padding: 24px 20px; - border-bottom: 1px solid $neutral200; - cursor: pointer; - - @include typography(tabLabel); - - &:hover { - font-weight: 600; - border-color: $neutral300; - } - - svg { - fill: $neutral400; - } - } - - &__alert { - &__left { - display: flex; - align-items: center; - gap: 4px; - - &__tooltip { - position: relative; - - .tooltipList { - display: none; - position: absolute; - left: -38px; - width: 20rem; - padding: 21px 8px 8px 16px; - margin-top: 2px; - border-radius: 8px; - - background-image: url("@/assets/icons/tooltipFrame.svg"); - background-repeat: no-repeat; - background-size: auto; - - list-style-type: disc; - list-style-position: inside; - - @include typography(captionSmall); - line-height: 1.8rem; - color: $neutral0; - - @include animate(showTooltip, 0.2s, ease-in, forwards); - - & > li > span { - margin-left: -5px; - } - } - - & > button { - display: block; - - &:focus ~ .tooltipList, - &:hover ~ .tooltipList { - display: block; - } - } - } - } - - &__button { - position: relative; - width: 5.6rem; - height: 3.2rem; - border-radius: 32px; - - .alertState { - position: absolute; - top: 0; - width: 3.2rem; - height: 3.2rem; - - background-color: $neutral0; - border-radius: 50%; - transition: all 0.2s ease-in; - } - - &.on { - background-color: $success300; - .alertState { - left: calc(100% - 3.2rem); - border: 2px solid $success300; - } - } - &.off { - background-color: $neutral200; - .alertState { - left: 0; - border: 2px solid $neutral200; - } - } - } - } - - .version { - color: $neutral400; - } - } -} - -@include keyframes(showTooltip) { - 0% { - transform: scale(0); - opacity: 0; - } - 100% { - transform: scale(1); - opacity: 1; - } } diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index 878ec283..95c83226 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -1,17 +1,14 @@ -import { useState } from "react"; -import { RiArrowRightSLine } from "react-icons/ri"; -import { useNavigate } from "react-router-dom"; - import styles from "./User.module.scss"; -import AlertIcon from "@/assets/icons/error-warning-line.svg?react"; -import MapPin from "@/assets/icons/mapPin.svg?react"; -import Pencil from "@/assets/icons/pencil.svg?react"; -import Star from "@/assets/icons/star.svg?react"; +import MypageList from "@/components/User/MypageList/MypageList"; +import Mywork from "@/components/User/Mywork/Mywork"; +import Profile from "@/components/User/Profile/Profile"; const userInfo = { nickname: "개발토끼", image: "https://avatars.githubusercontent.com/u/100336573?v=4", + provider: "KAKAO", + email: "test@gmail.com", }; function User() { @@ -19,115 +16,16 @@ function User() { // nickname: "", // profile: profileDefault, // }); - const [alertOn, setAlertOn] = useState( - Notification.permission === "granted" ? true : false, - ); - const navigate = useNavigate(); - - const onClickAlert = () => { - if (Notification.permission === "denied") { - return; - } - - setAlertOn(!alertOn); - }; return (

      마이페이지

      -
      -
      - -
      - -
      {userInfo.nickname}
      -
      - -
      - - -
      - - -
      - -
        -
      • { - navigate("/user/privacy"); - }} - > -
        계정 관리
        - -
      • - -
      • -
        - 알림 -
        - - -
          -
        • - - 구글 크롬(Chrome) 브라우저에 최적화 되어 있어 크롬 브라우저 - 사용을 권장합니다. - -
        • -
        • - - 브라우저의 알림 설정을 켜주셔야 서비스 알림을 받을 수 - 있습니다. - -
        • -
        -
        -
        + - -
      • + -
      • -
        버전 정보
        -
        1.1.0
        -
      • -
      +
      ); } diff --git a/src/pages/User/UserPrivacy/UserPrivacy.tsx b/src/pages/User/UserPrivacy/UserPrivacy.tsx index 5504218f..5006c2fe 100644 --- a/src/pages/User/UserPrivacy/UserPrivacy.tsx +++ b/src/pages/User/UserPrivacy/UserPrivacy.tsx @@ -5,6 +5,9 @@ import styles from "./UserPrivacy.module.scss"; import Header from "@/components/Auth/Header/Header"; const userInfo = { + nickname: "개발토끼", + image: "https://avatars.githubusercontent.com/u/100336573?v=4", + provider: "KAKAO", email: "test@gmail.com", }; diff --git a/src/types/user.ts b/src/types/user.ts new file mode 100644 index 00000000..c43dc904 --- /dev/null +++ b/src/types/user.ts @@ -0,0 +1,10 @@ +export interface UserInfo { + nickname: string; + image: string; + provider: string; + email: string; +} + +export interface ProfileProps { + userInfo: UserInfo; +} From 8415c6d47677e91019bb32f32cb3fc15415b318f Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 18:23:51 +0900 Subject: [PATCH 47/50] Refactor: seperate components from myspace page --- .../MySpaceBody/MySpaceBody.module.scss | 7 + src/components/MySpaceBody/MySpaceBody.tsx | 136 ++++++++++++++ .../MySpaceList/MySpaceList.module.scss | 65 +++++++ .../MySpaceBody/MySpaceList/MySpaceList.tsx | 45 +++++ .../MySpaceBody/Tab/Tab.module.scss | 42 +++++ src/components/MySpaceBody/Tab/Tab.tsx | 38 ++++ src/pages/User/MySpace/MySpace.module.scss | 111 ------------ src/pages/User/MySpace/MySpace.tsx | 166 +----------------- src/types/user.ts | 23 +++ 9 files changed, 358 insertions(+), 275 deletions(-) create mode 100644 src/components/MySpaceBody/MySpaceBody.module.scss create mode 100644 src/components/MySpaceBody/MySpaceBody.tsx create mode 100644 src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss create mode 100644 src/components/MySpaceBody/MySpaceList/MySpaceList.tsx create mode 100644 src/components/MySpaceBody/Tab/Tab.module.scss create mode 100644 src/components/MySpaceBody/Tab/Tab.tsx diff --git a/src/components/MySpaceBody/MySpaceBody.module.scss b/src/components/MySpaceBody/MySpaceBody.module.scss new file mode 100644 index 00000000..18178963 --- /dev/null +++ b/src/components/MySpaceBody/MySpaceBody.module.scss @@ -0,0 +1,7 @@ +@use "@/sass" as *; + +.container { + display: flex; + flex-direction: column; + gap: 40px; +} diff --git a/src/components/MySpaceBody/MySpaceBody.tsx b/src/components/MySpaceBody/MySpaceBody.tsx new file mode 100644 index 00000000..933718fa --- /dev/null +++ b/src/components/MySpaceBody/MySpaceBody.tsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from "react"; + +import styles from "./MySpaceBody.module.scss"; + +import MySpaceList from "./MySpaceList/MySpaceList"; +import Tab from "./Tab/Tab"; + +import { MySpaces } from "@/types/user"; + +const upcomming = [ + { + id: 12312, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-12", + endDate: "2024-01-14", + dueDate: 0, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 12321, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 4, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 12334, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 15, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 12343, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 15, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 12352, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 15, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 12315, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 15, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 123253, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2024-01-18", + endDate: "2024-01-22", + dueDate: 15, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 1231617, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2025-01-18", + endDate: "2025-01-22", + dueDate: 418, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, +]; + +const outdated = [ + { + id: 812367, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2023-12-17", + endDate: "2023-12-21", + dueDate: 0, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 867123, + title: "캐리비안베이, 로만바스 온천, 라마드 호텔, 에버랜드", + startDate: "2023-04-17", + endDate: "2023-04-20", + dueDate: 0, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, + { + id: 67123, + title: "강릉, 속초, 전주, 여수 여행", + startDate: "2022-12-29", + endDate: "2022-12-30", + dueDate: 0, + thumbnail: + "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", + }, +]; + +function MySpaceBody() { + const [tab, setTab] = useState("upcomming"); + const [data, setData] = useState(null); + + useEffect(() => { + // const res = await() api호출부 + // const spaces = res.data.data; + // tmp mock data + const spaces = tab === "upcomming" ? upcomming : outdated; + setData(spaces); + }); + + return ( +
      + + + +
      + ); +} + +export default MySpaceBody; diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss b/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss new file mode 100644 index 00000000..63f2013f --- /dev/null +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss @@ -0,0 +1,65 @@ +@use "@/sass" as *; + +.container { + display: flex; + flex-direction: column; + gap: 16px; + padding: 0 20px; + + & > li { + display: flex; + + height: 9rem; + border: 1px solid $neutral200; + border-radius: 16px; + } + + .img { + position: relative; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + + width: 6.6rem; + background-position: center; + background-size: cover; + background-blend-mode: darken; + border-radius: 16px 0px 0px 16px; + + & > span { + z-index: 100; + @include typography(button); + color: $neutral0; + } + + &::after { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.2); + border-radius: 16px 0px 0px 16px; + } + } + + .content { + flex: 1 1 auto; + padding: 20px 16px; + min-width: 0; + + &__title { + margin-bottom: 4px; + + @include typography(titleSmall); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &__date { + @include typography(bodySmall); + color: $neutral400; + } + } +} diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx new file mode 100644 index 00000000..0cc1f77b --- /dev/null +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx @@ -0,0 +1,45 @@ +import styles from "./MySpaceList.module.scss"; + +import { setMyspaceDate } from "@/utils/formatDate"; + +import { MySpaceListProps } from "@/types/user"; + +function MySpaceList({ data, tab }: MySpaceListProps) { + return ( +
        + {data?.map(({ id, title, startDate, endDate, dueDate, thumbnail }) => ( +
      • { + // navigate(`/trip/${id}`) + }} + > +
        + + {dueDate === 0 + ? tab === "upcomming" + ? "여행중" + : "" + : `D-${dueDate}`} + +
        + +
        +
        {title}
        + +
        + {setMyspaceDate(startDate, endDate)} +
        +
        +
      • + ))} +
      + ); +} + +export default MySpaceList; diff --git a/src/components/MySpaceBody/Tab/Tab.module.scss b/src/components/MySpaceBody/Tab/Tab.module.scss new file mode 100644 index 00000000..debe5f37 --- /dev/null +++ b/src/components/MySpaceBody/Tab/Tab.module.scss @@ -0,0 +1,42 @@ +@use "@/sass" as *; + +.container { + position: relative; + top: 0; + display: flex; + justify-content: space-around; + height: auto; + padding: 0; + padding-bottom: 12px; + cursor: pointer; + @include typography(tabLabel); + + target { + color: $neutral300; + width: 50%; + text-align: center; + + &.selected { + font-weight: 700; + color: $neutral900; + } + } + + .bar { + position: absolute; + bottom: 0; + + width: 50%; + height: 2px; + background-color: $primary300; + + transition: all 0.2s ease-out; + + &.left { + left: 0; + } + &.right { + left: 50%; + } + } +} diff --git a/src/components/MySpaceBody/Tab/Tab.tsx b/src/components/MySpaceBody/Tab/Tab.tsx new file mode 100644 index 00000000..17dd2a99 --- /dev/null +++ b/src/components/MySpaceBody/Tab/Tab.tsx @@ -0,0 +1,38 @@ +import styles from "./Tab.module.scss"; + +import { TabProps } from "@/types/user"; + +function Tab({ tab, setTab, data, setData }: TabProps) { + return ( + + ); +} + +export default Tab; diff --git a/src/pages/User/MySpace/MySpace.module.scss b/src/pages/User/MySpace/MySpace.module.scss index 976cf350..6da1160b 100644 --- a/src/pages/User/MySpace/MySpace.module.scss +++ b/src/pages/User/MySpace/MySpace.module.scss @@ -4,115 +4,4 @@ margin-top: 80px; margin-bottom: 72px; color: $neutral900; - - .myspace { - display: flex; - flex-direction: column; - gap: 40px; - - &__tab { - position: relative; - top: 0; - display: flex; - justify-content: space-around; - height: auto; - padding: 0; - padding-bottom: 12px; - cursor: pointer; - @include typography(tabLabel); - - &__target { - color: $neutral300; - width: 50%; - text-align: center; - - &.selected { - font-weight: 700; - color: $neutral900; - } - } - - .bar { - position: absolute; - bottom: 0; - - width: 50%; - height: 2px; - background-color: $primary300; - - transition: all 0.2s ease-out; - - &.left { - left: 0; - } - &.right { - left: 50%; - } - } - } - - &__list { - display: flex; - flex-direction: column; - gap: 16px; - padding: 0 20px; - - & > li { - display: flex; - - height: 9rem; - border: 1px solid $neutral200; - border-radius: 16px; - } - - &__img { - position: relative; - display: flex; - justify-content: center; - align-items: center; - flex-shrink: 0; - - width: 6.6rem; - background-position: center; - background-size: cover; - background-blend-mode: darken; - border-radius: 16px 0px 0px 16px; - - & > span { - z-index: 100; - @include typography(button); - color: $neutral0; - } - - &::after { - content: ""; - position: absolute; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.2); - border-radius: 16px 0px 0px 16px; - } - } - - &__content { - flex: 1 1 auto; - padding: 20px 16px; - min-width: 0; - - &__title { - margin-bottom: 4px; - - @include typography(titleSmall); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - &__date { - @include typography(bodySmall); - color: $neutral400; - } - } - } - } } diff --git a/src/pages/User/MySpace/MySpace.tsx b/src/pages/User/MySpace/MySpace.tsx index 8ef1b480..bb51e352 100644 --- a/src/pages/User/MySpace/MySpace.tsx +++ b/src/pages/User/MySpace/MySpace.tsx @@ -1,176 +1,14 @@ -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; - import styles from "./MySpace.module.scss"; import Header from "@/components/Auth/Header/Header"; - -import { setMyspaceDate } from "@/utils/formatDate"; - -const upcomming = [ - { - id: 12312, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-12", - endDate: "2024-01-14", - dueDate: 0, - }, - { - id: 12321, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 4, - }, - { - id: 12334, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - }, - { - id: 12343, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - }, - { - id: 12352, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - }, - { - id: 12315, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - }, - { - id: 123253, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - }, - { - id: 1231617, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2025-01-18", - endDate: "2025-01-22", - dueDate: 418, - }, -]; - -const outdated = [ - { - id: 812367, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2023-12-17", - endDate: "2023-12-21", - dueDate: 0, - }, - { - id: 867123, - title: "캐리비안베이, 로만바스 온천, 라마드 호텔, 에버랜드", - startDate: "2023-04-17", - endDate: "2023-04-20", - dueDate: 0, - }, - { - id: 67123, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2022-12-29", - endDate: "2022-12-30", - dueDate: 0, - }, -]; - -const tmpImage = - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc="; +import MySpaceBody from "@/components/MySpaceBody/MySpaceBody"; function MySpace() { - const [tab, setTab] = useState("upcomming"); - const [data, setData] = useState(upcomming); - - const navigate = useNavigate(); - return (
      -
      - - -
        - {data.map(({ id, title, startDate, endDate, dueDate }) => ( -
      • { - // navigate(`/trip/${id}`) - }} - > -
        - - {dueDate === 0 - ? tab === "upcomming" - ? "여행중" - : "" - : `D-${dueDate}`} - -
        - -
        -
        - {title} -
        - -
        - {setMyspaceDate(startDate, endDate)} -
        -
        -
      • - ))} -
      -
      +
      ); } diff --git a/src/types/user.ts b/src/types/user.ts index c43dc904..6c23eb1e 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -8,3 +8,26 @@ export interface UserInfo { export interface ProfileProps { userInfo: UserInfo; } + +export interface MySpaceData { + id: number; + title: string; + startDate: string; + endDate: string; + dueDate: number; + thumbnail: string; +} + +export type MySpaces = MySpaceData[]; + +export interface TabProps { + tab: string; + setTab: React.Dispatch>; + data: MySpaces | null; + setData: React.Dispatch>; +} + +export interface MySpaceListProps { + data: MySpaces | null; + tab: string; +} From 9b94d0996a717ebd85d169df34e15a452f0d90d8 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 19:11:45 +0900 Subject: [PATCH 48/50] Feat: add setSpaceDate_DOW util func --- .../MySpaceBody/MySpaceList/MySpaceList.tsx | 4 +- src/pages/User/MyReview/MyReview.tsx | 15 ++++--- src/utils/formatDate.ts | 44 ++++++++++++++++++- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx index 0cc1f77b..e47fb7d5 100644 --- a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx @@ -1,6 +1,6 @@ import styles from "./MySpaceList.module.scss"; -import { setMyspaceDate } from "@/utils/formatDate"; +import { setSpaceDate } from "@/utils/formatDate"; import { MySpaceListProps } from "@/types/user"; @@ -33,7 +33,7 @@ function MySpaceList({ data, tab }: MySpaceListProps) {
      {title}
      - {setMyspaceDate(startDate, endDate)} + {setSpaceDate(startDate, endDate)}
    diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx index 0f826df8..79552f2c 100644 --- a/src/pages/User/MyReview/MyReview.tsx +++ b/src/pages/User/MyReview/MyReview.tsx @@ -10,6 +10,7 @@ import ActionList from "@/components/MyReview/ActionList/ActionList"; import Meatball from "@/assets/icons/meatball.svg?react"; import Star from "@/assets/icons/star_fill.svg?react"; +import { setMyReviewDate } from "@/utils/formatDate"; const data = [ { @@ -22,7 +23,7 @@ const data = [ thumbnail: "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", }, - visitedAt: "2024년 1월 방문", + visitedAt: "2023-11-18", rating: "5.0", content: "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", @@ -46,7 +47,7 @@ const data = [ thumbnail: "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", }, - visitedAt: "2024년 1월 방문", + visitedAt: "2023-11-18", rating: "5.0", content: "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", @@ -68,7 +69,7 @@ const data = [ thumbnail: "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", }, - visitedAt: "2024년 1월 방문", + visitedAt: "2023-11-18", rating: "5.0", content: "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", @@ -90,7 +91,7 @@ const data = [ thumbnail: "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", }, - visitedAt: "2024년 1월 방문", + visitedAt: "2023-11-18", rating: "5.0", content: "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", @@ -112,7 +113,7 @@ const data = [ thumbnail: "https://cdn.safetimes.co.kr/news/photo/202210/115164_98919_3327.jpg", }, - visitedAt: "2024년 1월 방문", + visitedAt: "2023-11-18", rating: "5.0", content: "아주 좋아요. 자주 다니고 있어요. 친구들이랑 저녁에 운동하기 좋아요. 다음에 또 가고 싶네요", @@ -176,7 +177,9 @@ function MyReview() {
    {rating}
    -
    {visitedAt}
    +
    {`${setMyReviewDate(visitedAt)} 방문`}

    {content}

    diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts index ede0f6c3..f8460c04 100644 --- a/src/utils/formatDate.ts +++ b/src/utils/formatDate.ts @@ -6,7 +6,48 @@ const createDate = (date: string) => { return new Date(arrayForm[0], arrayForm[1] - 1, arrayForm[2]); }; -export const setMyspaceDate = (start: string, end: string) => { +const changeDOWFormat = (format: string) => { + let result = format; + + if (result.includes("Su")) { + result = result.replace(/Su/g, "일"); + } + if (result.includes("Mo")) { + result = result.replace(/Mo/g, "월"); + } + if (result.includes("Tu")) { + result = result.replace(/Tu/g, "화"); + } + if (result.includes("We")) { + result = result.replace(/We/g, "수"); + } + if (result.includes("Th")) { + result = result.replace(/Th/g, "목"); + } + if (result.includes("Fr")) { + result = result.replace(/Fr/g, "금"); + } + if (result.includes("Sa")) { + result = result.replace(/Sa/g, "토"); + } + + return result; +}; + +// ex) 1.17(금) - 1.19(일) +export const setSpaceDate_DOW = (start: string, end: string) => { + const startFormat = format(createDate(start), "M.dd(EEEEEE)"); + const endFormat = format(createDate(end), "M.dd(EEEEEE)"); + + if (end) { + return changeDOWFormat(`${startFormat} - ${endFormat}`); + } else { + return changeDOWFormat(startFormat); + } +}; + +// ex) 2024.01.12 - 2024.01.14 +export const setSpaceDate = (start: string, end: string) => { const startFormat = format(createDate(start), "yyyy.MM.dd"); const endFormat = format(createDate(end), "yyyy.MM.dd"); @@ -17,6 +58,7 @@ export const setMyspaceDate = (start: string, end: string) => { } }; +// ex) 2024년 6월 export const setMyReviewDate = (visitedAt: string) => { const visitFormat = format(createDate(visitedAt), "yyyy'년' M'월'"); return visitFormat; From 5c66b9b54d9c4b95bb12f95399c8a58474990949 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Mon, 22 Jan 2024 19:49:48 +0900 Subject: [PATCH 49/50] Hotfix: fix errors --- src/components/Auth/Input/InputOldPassword.tsx | 8 +++++++- src/components/Auth/Signup/SignupForm.tsx | 2 +- src/components/MySpaceBody/MySpaceBody.tsx | 2 +- src/components/MySpaceBody/Tab/Tab.tsx | 2 +- src/types/auth.ts | 14 +++++++++++++- src/types/user.ts | 2 -- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/Auth/Input/InputOldPassword.tsx b/src/components/Auth/Input/InputOldPassword.tsx index 9d768e4b..c38680fe 100644 --- a/src/components/Auth/Input/InputOldPassword.tsx +++ b/src/components/Auth/Input/InputOldPassword.tsx @@ -2,7 +2,13 @@ import styles from "./Input.module.scss"; import validationForm from "@/utils/inputValidation"; -function InputOldPassword({ register, dirtyFields, errors }) { +import { InputOldPasswordProps } from "@/types/auth"; + +function InputOldPassword({ + register, + dirtyFields, + errors, +}: InputOldPasswordProps) { return (
    diff --git a/src/components/Auth/Signup/SignupForm.tsx b/src/components/Auth/Signup/SignupForm.tsx index ce18256d..3daf72a3 100644 --- a/src/components/Auth/Signup/SignupForm.tsx +++ b/src/components/Auth/Signup/SignupForm.tsx @@ -41,7 +41,7 @@ function SignupForm({ signupStep, setSignupStep }: SignupFormProps) { console.log(data); try { - const { email, password, image, nickname } = data; + const { email, password, nickname } = data; const res = await axios.post("/api/auth/register", { email, password, diff --git a/src/components/MySpaceBody/MySpaceBody.tsx b/src/components/MySpaceBody/MySpaceBody.tsx index 933718fa..ccb5eeae 100644 --- a/src/components/MySpaceBody/MySpaceBody.tsx +++ b/src/components/MySpaceBody/MySpaceBody.tsx @@ -126,7 +126,7 @@ function MySpaceBody() { return (
    - +
    diff --git a/src/components/MySpaceBody/Tab/Tab.tsx b/src/components/MySpaceBody/Tab/Tab.tsx index 17dd2a99..b67d124d 100644 --- a/src/components/MySpaceBody/Tab/Tab.tsx +++ b/src/components/MySpaceBody/Tab/Tab.tsx @@ -2,7 +2,7 @@ import styles from "./Tab.module.scss"; import { TabProps } from "@/types/user"; -function Tab({ tab, setTab, data, setData }: TabProps) { +function Tab({ tab, setTab }: TabProps) { return (