From 44985f9b35c0b7bc49424f98b90d4c4dc1583f80 Mon Sep 17 00:00:00 2001 From: woojoung Date: Thu, 16 Jan 2025 15:17:14 +0900 Subject: [PATCH 01/26] =?UTF-8?q?Feat:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20console=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/User/Auth.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/User/Auth.tsx b/src/pages/User/Auth.tsx index c95f6d9..f53598c 100644 --- a/src/pages/User/Auth.tsx +++ b/src/pages/User/Auth.tsx @@ -12,8 +12,6 @@ const Auth = () => { const handleKakaoLogin = () => { const KAKAO_CLIENT_ID = import.meta.env.VITE_KAKAO_CLIENT_ID; const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI; - console.log('KAKAO_CLIENT_ID:', KAKAO_CLIENT_ID); - console.log('REDIRECT_URI:', REDIRECT_URI); // 카카오 공식 OAuth 엔드포인트 사용 window.location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code`; }; From d90f71985eee7bc0d7b0e98b6e897cece4eb466a Mon Sep 17 00:00:00 2001 From: kyungmim Date: Thu, 16 Jan 2025 17:58:05 +0900 Subject: [PATCH 02/26] =?UTF-8?q?Feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-arrow-right-black.svg | 3 + public/img/icon-bookmark-studio.svg | 3 + public/img/icon-myreview.svg | 4 + src/pages/User/MyPage.tsx | 125 +++++++++++++++++++++++--- 4 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 public/img/icon-arrow-right-black.svg create mode 100644 public/img/icon-bookmark-studio.svg create mode 100644 public/img/icon-myreview.svg diff --git a/public/img/icon-arrow-right-black.svg b/public/img/icon-arrow-right-black.svg new file mode 100644 index 0000000..f6b79b7 --- /dev/null +++ b/public/img/icon-arrow-right-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icon-bookmark-studio.svg b/public/img/icon-bookmark-studio.svg new file mode 100644 index 0000000..a2a4305 --- /dev/null +++ b/public/img/icon-bookmark-studio.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icon-myreview.svg b/public/img/icon-myreview.svg new file mode 100644 index 0000000..6207bab --- /dev/null +++ b/public/img/icon-myreview.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/pages/User/MyPage.tsx b/src/pages/User/MyPage.tsx index 16588ca..1c9132f 100644 --- a/src/pages/User/MyPage.tsx +++ b/src/pages/User/MyPage.tsx @@ -1,23 +1,120 @@ -import { useUserStore } from '@store/useUserStore'; -import { useNavigate } from 'react-router-dom'; +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import variables from '@styles/Variables'; +import Header from '@components/Header/Header'; +import { Link } from 'react-router-dom'; +import { IUser } from 'types/types'; +import { defaultUserState } from '@store/useUserStore'; +import { getLocalStorageItem } from '@utils/getLocalStorageItem'; +import { TypoBodyMdR, TypoTitleMdSb, TypoTitleXsR } from '@styles/Common'; const MyPage = () => { - const logout = useUserStore((state) => state.resetUser); - const navigate = useNavigate(); - - const handleClick = () => { - logout(); - navigate('/'); - }; - + const { username, email } = getLocalStorageItem('userState', defaultUserState); return ( <> -

마이페이지

- +
+
+ {username}님 환영해요! +

{email}

+
+ +
    +
  • + 예약내역 +
  • +
  • + 내 리뷰 +
  • +
  • + 찜한 사진관 +
  • +
); }; export default MyPage; + +const MyInfoStyle = css` + display: flex; + flex-direction: column; + gap: 0.4rem; + padding: 1.6rem 0; + + & a { + ${TypoTitleMdSb} + display: flex; + gap: 0.4rem; + align-items: center; + + &::after { + content: ''; + width: 2.4rem; + height: 2.4rem; + background-image: url('/img/icon-arrow-right-black.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: 1rem; + } + } + + & p { + ${TypoBodyMdR} + color:${variables.colors.gray800} + } +`; + +const MyPageMenuStyle = css` + &::before { + content: ''; + display: block; + background-color: ${variables.colors.gray300}; + height: 1rem; + width: calc(100% + (1.6rem * 2)); + margin-left: -1.6rem; + } + + & li { + & a { + padding: 1.6rem 0; + border-bottom: 0.1rem solid ${variables.colors.gray300}; + ${TypoTitleXsR} + display: flex; + gap: 1.6rem; + align-items: center; + + &::after { + content: ''; + width: 2.4rem; + height: 2.4rem; + background-image: url('/img/icon-arrow-16.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: 1rem; + margin-left: auto; + } + + &::before { + content: ''; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: center; + background-size: 2rem; + } + } + } + + .history > a::before { + background-image: url('/img/icon-calendar-black.svg'); + } + .myreview > a::before { + background-image: url('/img/icon-myreview.svg'); + } + .bookmarkstudio > a::before { + background-image: url('/img/icon-bookmark-studio.svg'); + } + .bookmarkstudio > a { + border-bottom: none; + } +`; From aa5e9e31560421618cde97d91cdfb49f0990cff7 Mon Sep 17 00:00:00 2001 From: woojoung Date: Fri, 17 Jan 2025 12:54:37 +0900 Subject: [PATCH 03/26] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Studio/StudioReview/StudioReviewWritePage.tsx | 9 +++++++++ src/routes.tsx | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 src/pages/Studio/StudioReview/StudioReviewWritePage.tsx diff --git a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx new file mode 100644 index 0000000..a7305ae --- /dev/null +++ b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx @@ -0,0 +1,9 @@ +import { useParams } from 'react-router-dom'; + +const StudioReviewWritePage = () => { + const { _id } = useParams(); + + return
{_id}
; +}; + +export default StudioReviewWritePage; diff --git a/src/routes.tsx b/src/routes.tsx index 5af77ba..dd9ddf9 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,3 +1,4 @@ +import StudioReviewWritePage from '@pages/Studio/StudioReview/StudioReviewWritePage'; import Auth from '@pages/User/Auth'; import AuthVerification from '@pages/User/AuthVerification'; import KakaoCallback from '@pages/User/KakaoCallback'; @@ -119,6 +120,7 @@ const router = createBrowserRouter([ { path: 'photos', element: }, ], }, + { path: 'review/write', element: }, { path: 'reservation', children: [ From 1450ddd040665b3806e956efe21db33fe083f6f8 Mon Sep 17 00:00:00 2001 From: woojoung Date: Fri, 17 Jan 2025 13:45:40 +0900 Subject: [PATCH 04/26] =?UTF-8?q?Feat:=20starInput=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=82=AC=EC=A7=84=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-star-filled.svg | 3 + public/img/icon-star-notfilled.svg | 3 + .../StudioReview/StudioReviewWritePage.tsx | 32 ++++++++- .../StudioReview/components/StarInput.tsx | 71 +++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 public/img/icon-star-filled.svg create mode 100644 public/img/icon-star-notfilled.svg create mode 100644 src/pages/Studio/StudioReview/components/StarInput.tsx diff --git a/public/img/icon-star-filled.svg b/public/img/icon-star-filled.svg new file mode 100644 index 0000000..af1a531 --- /dev/null +++ b/public/img/icon-star-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icon-star-notfilled.svg b/public/img/icon-star-notfilled.svg new file mode 100644 index 0000000..207ed24 --- /dev/null +++ b/public/img/icon-star-notfilled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx index a7305ae..d5aa081 100644 --- a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx +++ b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx @@ -1,9 +1,39 @@ +/** @jsxImportSource @emotion/react */ +import Header from '@components/Header/Header'; +import { css } from '@emotion/react'; +import { TypoTitleXsM } from '@styles/Common'; +import variables from '@styles/Variables'; import { useParams } from 'react-router-dom'; +import StarInput from './components/StarInput'; const StudioReviewWritePage = () => { const { _id } = useParams(); - return
{_id}
; + return ( +
+
+

+ 촬영 어떠셨나요? +

+
+ 내용1 +
+ +
+ ); }; export default StudioReviewWritePage; diff --git a/src/pages/Studio/StudioReview/components/StarInput.tsx b/src/pages/Studio/StudioReview/components/StarInput.tsx new file mode 100644 index 0000000..7632c92 --- /dev/null +++ b/src/pages/Studio/StudioReview/components/StarInput.tsx @@ -0,0 +1,71 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { TypoBodyMdR } from '@styles/Common'; +import variables from '@styles/Variables'; +import { useState } from 'react'; + +const StarInput = () => { + const [starState, setStarState] = useState([0, 0, 0, 0, 0]); + + const getRatingText = (stars: number[]) => { + const count = stars.filter((s) => s === 1).length; + switch (count) { + case 1: + return '별로였어요'; + case 2: + return '그저 그랬어요'; + case 3: + return '괜찮았어요'; + case 4: + return '좋았어요'; + case 5: + return '최고였어요!'; + default: + return '별점을 입력해주세요'; + } + }; + + return ( +
+ {starState.map((star, index) => ( + 별점 { + const newStarState = Array(5) + .fill(0) + .map((_, i) => (i <= index ? 1 : 0)); + setStarState(newStarState); + }} + /> + ))} + + {getRatingText(starState)} + +
+ ); +}; + +export default StarInput; From f4590f6837a939e282fca5d954900d3264e8997b Mon Sep 17 00:00:00 2001 From: kyungmim Date: Fri, 17 Jan 2025 14:23:56 +0900 Subject: [PATCH 05/26] =?UTF-8?q?Feat:=20=EC=9D=B4=EC=9A=A9=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=B9=A9=20UI=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20typo?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationCard/ReservationCard.tsx | 15 ++++++++++ src/components/ReservationCard/StatusChip.tsx | 29 +++++++++++++++++++ src/pages/User/MyPage.tsx | 9 ++++++ src/styles/Common.tsx | 5 ++++ 4 files changed, 58 insertions(+) create mode 100644 src/components/ReservationCard/ReservationCard.tsx create mode 100644 src/components/ReservationCard/StatusChip.tsx diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx new file mode 100644 index 0000000..ab677f9 --- /dev/null +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -0,0 +1,15 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import variables from '@styles/Variables'; +import StatusChip from './StatusChip'; + +const ReservationCard = () => { + return ( + <> + 카드 + ; + + ); +}; + +export default ReservationCard; diff --git a/src/components/ReservationCard/StatusChip.tsx b/src/components/ReservationCard/StatusChip.tsx new file mode 100644 index 0000000..9cdced0 --- /dev/null +++ b/src/components/ReservationCard/StatusChip.tsx @@ -0,0 +1,29 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { TypoCapXsR } from '@styles/Common'; +import variables from '@styles/Variables'; + +type ChipType = { + state: 'confirmed' | 'pending' | 'completed' | 'canceled'; + //예약 확정 | 예약 확인 중 | 이용 완료 | 예약 취소 +}; + +const StatusChip = ({ state = 'pending' }: ChipType) => { + return
이용상태
; +}; + +export default StatusChip; + +const ChipStyle = (state: string) => css` + display: inline-block; + padding: 0.4rem; + border-radius: 0.4rem; + text-align: center; + ${TypoCapXsR} + + ${state === 'confirmed' && `background-color: ${variables.colors.primary200};`} + ${state === 'pending' && `background-color: ${variables.colors.gray300};`} + ${state === 'completed' && + `background-color: ${variables.colors.gray300}; color: ${variables.colors.gray700}`} + ${state === 'canceled' && `background-color: #FFE1E1; color:#E00100`} +`; diff --git a/src/pages/User/MyPage.tsx b/src/pages/User/MyPage.tsx index 1c9132f..cc65ea8 100644 --- a/src/pages/User/MyPage.tsx +++ b/src/pages/User/MyPage.tsx @@ -7,6 +7,7 @@ import { IUser } from 'types/types'; import { defaultUserState } from '@store/useUserStore'; import { getLocalStorageItem } from '@utils/getLocalStorageItem'; import { TypoBodyMdR, TypoTitleMdSb, TypoTitleXsR } from '@styles/Common'; +import ReservationCard from '@components/ReservationCard/ReservationCard'; const MyPage = () => { const { username, email } = getLocalStorageItem('userState', defaultUserState); @@ -18,6 +19,10 @@ const MyPage = () => {

{email}

+
+ +
+
  • 예약내역 @@ -118,3 +123,7 @@ const MyPageMenuStyle = css` border-bottom: none; } `; + +const box = css` + box-shadow: inset 0 0 10px black; +`; diff --git a/src/styles/Common.tsx b/src/styles/Common.tsx index 889ef54..e920265 100644 --- a/src/styles/Common.tsx +++ b/src/styles/Common.tsx @@ -73,6 +73,11 @@ export const TypoCapSmM = css` font-weight: 500; line-height: 1.4rem; `; +export const TypoCapXsR = css` + font-size: 1rem; + font-weight: 400; + line-height: 1.2rem; +`; export const DividerStyle = css` position: relative; From c3ba79284ccee433d5a1646d6c01acab857840cf Mon Sep 17 00:00:00 2001 From: kyungmim Date: Fri, 17 Jan 2025 14:28:46 +0900 Subject: [PATCH 06/26] =?UTF-8?q?Add:=20=EC=8A=A4=ED=8A=9C=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B8=B0=EC=A1=B4=20=EC=8A=A4=ED=8A=9C=EB=94=94?= =?UTF-8?q?=EC=98=A4=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/{icon-bookmark-studio.svg => icon-studio-black.svg} | 0 public/img/icon-studio-yellow.svg | 3 +++ src/pages/User/MyPage.tsx | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) rename public/img/{icon-bookmark-studio.svg => icon-studio-black.svg} (100%) create mode 100644 public/img/icon-studio-yellow.svg diff --git a/public/img/icon-bookmark-studio.svg b/public/img/icon-studio-black.svg similarity index 100% rename from public/img/icon-bookmark-studio.svg rename to public/img/icon-studio-black.svg diff --git a/public/img/icon-studio-yellow.svg b/public/img/icon-studio-yellow.svg new file mode 100644 index 0000000..5ec5faa --- /dev/null +++ b/public/img/icon-studio-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/User/MyPage.tsx b/src/pages/User/MyPage.tsx index cc65ea8..f38cc81 100644 --- a/src/pages/User/MyPage.tsx +++ b/src/pages/User/MyPage.tsx @@ -117,7 +117,7 @@ const MyPageMenuStyle = css` background-image: url('/img/icon-myreview.svg'); } .bookmarkstudio > a::before { - background-image: url('/img/icon-bookmark-studio.svg'); + background-image: url('/img/icon-studio-black.svg'); } .bookmarkstudio > a { border-bottom: none; From a08bcd9e965cbadaa49eb60675ca2cbbb84d5b15 Mon Sep 17 00:00:00 2001 From: JWJung-99 <39busy@naver.com> Date: Fri, 17 Jan 2025 14:46:50 +0900 Subject: [PATCH 07/26] =?UTF-8?q?Cont:=20=EC=98=88=EC=95=BD=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-noreservation.svg | 3 + .../Navigator/ReservationNavigator.tsx | 97 ++++++++++ src/hooks/useGetReservationList.ts | 31 +++ src/pages/Reservation/ReservationList.tsx | 181 +++++++++++++++++- 4 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 public/img/icon-noreservation.svg create mode 100644 src/components/Navigator/ReservationNavigator.tsx create mode 100644 src/hooks/useGetReservationList.ts diff --git a/public/img/icon-noreservation.svg b/public/img/icon-noreservation.svg new file mode 100644 index 0000000..ae03bf7 --- /dev/null +++ b/public/img/icon-noreservation.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Navigator/ReservationNavigator.tsx b/src/components/Navigator/ReservationNavigator.tsx new file mode 100644 index 0000000..0e7f916 --- /dev/null +++ b/src/components/Navigator/ReservationNavigator.tsx @@ -0,0 +1,97 @@ +/** @jsxImportSource @emotion/react */ +import styled from '@emotion/styled'; +import { ResStatus } from '@pages/Reservation/ReservationList'; +import { TypoTitleXsM } from '@styles/Common'; +import variables from '@styles/Variables'; +import { Dispatch, SetStateAction } from 'react'; + +const ReservationNavigator = ({ + list, + status, + setStatus, +}: { + list: ResStatus[]; + status: ResStatus; + setStatus: Dispatch>; +}) => { + return ( + + ); +}; + +const UlStyle = styled.ul` + display: flex; +`; + +const LiStyle = styled.li<{ length: number }>` + position: relative; + width: calc(100% / length); + flex-grow: 1; + flex-shrink: 0; + + display: flex; + justify-content: center; + align-items: center; + padding: 1rem 0; + box-sizing: border-box; + color: ${variables.colors.gray600}; + + & > span { + position: relative; + } + + &::before { + content: ''; + position: absolute; + background-color: ${variables.colors.gray300}; + height: 0.1rem; + left: 0; + right: 0; + bottom: 0; + } + + &.active { + color: ${variables.colors.black}; + } + + &.active::before { + content: ''; + position: absolute; + background-color: ${variables.colors.black}; + height: 0.2rem; + left: 0; + right: 0; + bottom: 0; + } + + &.active > span::after { + content: ''; + position: absolute; + right: calc(-4 * sqrt(2) * 0.1rem); + top: calc(-2 * sqrt(2) * 0.1rem); + width: 0.4rem; + height: 0.4rem; + background-color: ${variables.colors.primary}; + transform: translateX(-25%) rotate(45deg); + } +`; + +export default ReservationNavigator; diff --git a/src/hooks/useGetReservationList.ts b/src/hooks/useGetReservationList.ts new file mode 100644 index 0000000..9142556 --- /dev/null +++ b/src/hooks/useGetReservationList.ts @@ -0,0 +1,31 @@ +import { ResStatus } from '@pages/Reservation/ReservationList'; +import { useQuery } from '@tanstack/react-query'; + +// 예약 상태 별 예약 내역을 불러오는 hook +// api 완성되면 연동 +const fetchReservationList = async (resStatus: ResStatus, accessToken: string) => { + console.log(accessToken); + + const response = await fetch(`${import.meta.env.VITE_TOUCHEESE_API}/${resStatus}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + // Authorization: Bearer ${accessToken} + }, + }); + + if (!response.ok) { + console.error('Failed to fetch data'); + } + + return response.json(); +}; + +export const useGetReservationList = (resStatus: ResStatus, accessToken: string) => { + return useQuery({ + queryKey: [resStatus], + queryFn: () => fetchReservationList(resStatus, accessToken), + staleTime: 1000 * 60 * 1, + refetchOnWindowFocus: false, + }); +}; diff --git a/src/pages/Reservation/ReservationList.tsx b/src/pages/Reservation/ReservationList.tsx index 929e66a..b432853 100644 --- a/src/pages/Reservation/ReservationList.tsx +++ b/src/pages/Reservation/ReservationList.tsx @@ -1,5 +1,184 @@ +/** @jsxImportSource @emotion/react */ +import Header from '@components/Header/Header'; +import ReservationNavigator from '@components/Navigator/ReservationNavigator'; +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { TypoBodyMdM, TypoTitleXsM } from '@styles/Common'; +import variables from '@styles/Variables'; +import { useEffect, useState } from 'react'; + +export type ResStatus = '이용 예정' | '이용 완료' | '예약 취소'; +const STATUS: ResStatus[] = ['이용 예정', '이용 완료', '예약 취소']; + +interface IResItem { + id: number; + status: string; + studio: string; + menu: string; + menuImage: string; + date: string; + time: string; +} + +// 임시 데이터 +const reserved: IResItem[] = [ + { + id: 2, + status: 'confirmed', + studio: '모노 멘션', + menu: '상반신 촬영', + menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + date: '2025-01-25', + time: '13:00', + }, + { + id: 1, + status: 'pending', + studio: '모노 멘션', + menu: '상반신 촬영', + menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + date: '2025-01-18', + time: '13:00', + }, +]; + +const completed: IResItem[] = [ + { + id: 4, + status: 'completed', + studio: '모노 멘션', + menu: '상반신 촬영', + menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + date: '2025-01-25', + time: '13:00', + }, + { + id: 3, + status: 'completed', + studio: '모노 멘션', + menu: '상반신 촬영', + menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + date: '2025-01-12', + time: '13:00', + }, +]; + +const canceled: IResItem[] = []; + const ReservationList = () => { - return <>예약 정보; + const [resStatus, setResStatus] = useState('이용 예정'); + const [data, setData] = useState([]); + + // 로컬 스토리지에서 토큰 꺼내기 + // const { accessToken } = getLocalStorageItem('userState', defaultUserState); + + // resStatus 변경 시 api 호출 + useEffect(() => { + // if (accessToken) { + // const result = useGetReservationList(resStatus, accessToken); + // } + + // 임시로 데이터 set + if (resStatus === '이용 예정') setData(reserved); + else if (resStatus === '이용 완료') setData(completed); + else setData(canceled); + }, [resStatus]); + + // 예약 내역이 없을 시 메시지 생성 + let emptyMessage; + switch (resStatus) { + case '이용 예정': + emptyMessage = ( +

    + 예약한 사진관이 없습니다. +
    + 사진관을 예약하고 인생 사진을 +
    + 찍어보세요! +

    + ); + break; + case '이용 완료': + emptyMessage =

    방문하신 사진관이 없습니다.

    ; + break; + case '예약 취소': + emptyMessage =

    예약 취소하신 사진관이 없습니다.

    ; + break; + } + + return ( + <> + +
    + + + + + {data.length ? ( + +

    총 {data.length}건

    +
    + ) : ( + emptyMessage + )} +
    + + ); }; +const HeaderContainerStyle = styled.div` + background-color: ${variables.colors.white}; + position: fixed; + top: 0; + left: 0; + right: 0; + padding-top: 4rem; +`; + +const SectionStyle = styled.section` + margin: 0 calc(-1 * ${variables.layoutPadding}) -8.8rem; + background-color: ${variables.colors.gray100}; + padding: 11.6rem ${variables.layoutPadding} calc(4rem + ${variables.headerHeight}); + height: calc(100vh - 4rem); + overflow-y: auto; + + &.empty { + display: flex; + justify-content: center; + align-items: center; + color: ${variables.colors.gray700}; + + & > p { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.733rem; + text-align: center; + + &::before { + content: ''; + width: 4.2rem; + height: 4.2rem; + background-image: url('/img/icon-noreservation.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + } + } + } +`; + +const ContentStyle = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; +`; + +const headerStyle = css` + display: flex; + align-items: center; + padding: 1.6rem; +`; + export default ReservationList; From 21603675acfe4f479f80d71fe488f9670976c85c Mon Sep 17 00:00:00 2001 From: woojoung Date: Fri, 17 Jan 2025 15:10:47 +0900 Subject: [PATCH 08/26] =?UTF-8?q?Feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=B0=8F=20=ED=94=84=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StudioReview/StudioReviewWritePage.tsx | 106 +++++++++---- .../components/ImageUploadPreview.tsx | 140 ++++++++++++++++++ .../StudioReview/components/StarInput.tsx | 83 ++++++----- 3 files changed, 265 insertions(+), 64 deletions(-) create mode 100644 src/pages/Studio/StudioReview/components/ImageUploadPreview.tsx diff --git a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx index d5aa081..a0684ad 100644 --- a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx +++ b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx @@ -1,38 +1,94 @@ /** @jsxImportSource @emotion/react */ import Header from '@components/Header/Header'; import { css } from '@emotion/react'; -import { TypoTitleXsM } from '@styles/Common'; +import { DividerStyle, TypoTitleXsM } from '@styles/Common'; import variables from '@styles/Variables'; -import { useParams } from 'react-router-dom'; import StarInput from './components/StarInput'; +import Button from '@components/Button/Button'; +import ImageUploadPreview from './components/ImageUploadPreview'; +/** 리뷰 작성 페이지 */ const StudioReviewWritePage = () => { - const { _id } = useParams(); + // 별점 변경 + const handleRatingChange = (rating: number) => { + console.log('선택된 별점:', rating); + }; + + // 이미지 등록 + const handleImagesChange = (images: string[]) => { + console.log('현재 선택된 이미지들:', images); + }; return ( -
    +
    -

    - 촬영 어떠셨나요? -

    -
    - 내용1 -
    - -
    + +
    +

    + 촬영 어떠셨나요? +

    +
    + 내용1 +
    + +
    + +
    + +
    +

    + 사진 첨부 +

    +
    + +
    +
    + +
    + +
    +

    + 리뷰 작성 +

    +
    + 리뷰 작성 섹션 +
    +
    + + + + ))} + + {selectedImages.length < maxImages && ( + + )} + + ); +}; + +export default ImageUploadPreview; diff --git a/src/pages/Studio/StudioReview/components/StarInput.tsx b/src/pages/Studio/StudioReview/components/StarInput.tsx index 7632c92..b6b4050 100644 --- a/src/pages/Studio/StudioReview/components/StarInput.tsx +++ b/src/pages/Studio/StudioReview/components/StarInput.tsx @@ -4,25 +4,45 @@ import { TypoBodyMdR } from '@styles/Common'; import variables from '@styles/Variables'; import { useState } from 'react'; -const StarInput = () => { - const [starState, setStarState] = useState([0, 0, 0, 0, 0]); +// 별점 상수 5개 +const TOTAL_STARS = 5; - const getRatingText = (stars: number[]) => { - const count = stars.filter((s) => s === 1).length; - switch (count) { - case 1: - return '별로였어요'; - case 2: - return '그저 그랬어요'; - case 3: - return '괜찮았어요'; - case 4: - return '좋았어요'; - case 5: - return '최고였어요!'; - default: - return '별점을 입력해주세요'; - } +// 별점 텍스트 점수 따라 변환 +const RATING_TEXT: { [key: number]: string } = { + 0: '별점을 입력해주세요', + 1: '별로였어요', + 2: '그저 그랬어요', + 3: '괜찮았어요', + 4: '좋았어요', + 5: '최고였어요!', +}; + +// 별점 스타일 +const starStyles = css` + margin-right: 5px; + cursor: pointer; + transition: transform 0.2s ease; + &:hover { + transform: scale(1.2); + } + &:active { + transform: scale(0.9); + } +`; + +// 별점 컴포넌트 부모 컴포넌트로 전송 함수 +interface StarInputProps { + onRatingChange: (rating: number) => void; +} + +/** 별점 등록 컴포넌트 */ +const StarInput = ({ onRatingChange }: StarInputProps) => { + const [rating, setRating] = useState(0); + + const handleStarClick = (clickedIndex: number) => { + const newRating = clickedIndex + 1; + setRating(newRating); + onRatingChange(newRating); }; return ( @@ -32,28 +52,13 @@ const StarInput = () => { align-items: center; `} > - {starState.map((star, index) => ( + {Array.from({ length: TOTAL_STARS }, (_, index) => ( 별점 { - const newStarState = Array(5) - .fill(0) - .map((_, i) => (i <= index ? 1 : 0)); - setStarState(newStarState); - }} + src={index < rating ? '/img/icon-star-filled.svg' : '/img/icon-star-notfilled.svg'} + alt={`별점 ${index + 1}점`} + css={starStyles} + onClick={() => handleStarClick(index)} /> ))} { color: ${variables.colors.gray600}; `} > - {getRatingText(starState)} + {RATING_TEXT[rating]} ); From ee9c27850f2d45ed5b1ae26ad21343ca91604aa3 Mon Sep 17 00:00:00 2001 From: woojoung Date: Fri, 17 Jan 2025 15:31:46 +0900 Subject: [PATCH 09/26] =?UTF-8?q?Feat:=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes.tsx b/src/routes.tsx index dd9ddf9..518d6d5 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -120,7 +120,7 @@ const router = createBrowserRouter([ { path: 'photos', element: }, ], }, - { path: 'review/write', element: }, + { path: 'reservation', children: [ @@ -152,6 +152,7 @@ const router = createBrowserRouter([ children: [ { index: true, element: }, { path: 'canceled', element: }, + { path: 'review/write', element: }, ], }, ], From 56f4d8fc2bc5026a7f0316ac162d132429b33490 Mon Sep 17 00:00:00 2001 From: woojoung Date: Fri, 17 Jan 2025 15:50:50 +0900 Subject: [PATCH 10/26] =?UTF-8?q?Feat:=20=EC=8B=9C=EB=A9=98=ED=8B=B1=20?= =?UTF-8?q?=EB=A7=88=ED=81=AC=EC=97=85=20=EA=B3=84=EC=B8=B5=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Studio/StudioReview/StudioReviewWritePage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx index a0684ad..1e9dd2a 100644 --- a/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx +++ b/src/pages/Studio/StudioReview/StudioReviewWritePage.tsx @@ -24,14 +24,14 @@ const StudioReviewWritePage = () => {
    -

    촬영 어떠셨나요? -

    +
    {
    -

    { `} > 사진 첨부 -

    +
    @@ -66,14 +66,14 @@ const StudioReviewWritePage = () => {
    -

    리뷰 작성 -

    +
    Date: Fri, 17 Jan 2025 17:20:33 +0900 Subject: [PATCH 11/26] =?UTF-8?q?Done:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B3=B8=EC=9D=B8=EC=9D=B8=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C,=20LocalSto?= =?UTF-8?q?rage=20->=20SessionStorage=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20Hotf?= =?UTF-8?q?ix:=20useEffect=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/User/AuthVerification.tsx | 72 ++++++++++++++++++----------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/pages/User/AuthVerification.tsx b/src/pages/User/AuthVerification.tsx index eb9295d..dbf0e19 100644 --- a/src/pages/User/AuthVerification.tsx +++ b/src/pages/User/AuthVerification.tsx @@ -6,10 +6,10 @@ import Input from '@components/Input/Input'; import { css } from '@emotion/react'; import useSignupStore from '@store/useSignupStore'; import { TypoTitleMdSb } from '@styles/Common'; -import { useEffect, useState } from 'react'; +import { useLayoutEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; interface AuthVerificationType { success: boolean; @@ -27,13 +27,40 @@ const AuthRedirectURI = import.meta.env.VITE_AUTH_REDIRECT_URI; const AuthVerification = () => { /** zustand 스토어에 데이터 저장 */ - const { setSignupData, phone, name } = useSignupStore(); + const { setSignupData } = useSignupStore(); + const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [isActive, setIsActive] = useState(false); + const storedData = sessionStorage.getItem('signup-storage'); + const parsedData = storedData ? JSON.parse(storedData) : null; + const storageName = parsedData?.state?.name || ''; + const storagePhone = parsedData?.state?.phone || ''; + + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + // form 초기값을 sessionStorage 데이터로 설정 + defaultValues: { + name: storageName, + phone: storagePhone, + }, + }); + + /** 페이지가 처음 로드될 때 zustand 상태를 react-hook-form에 반영 */ + useLayoutEffect(() => { + if (searchParams.get('success')) { + setIsActive(true); + setSignupData({ name: storageName, phone: storagePhone }); + reset(); + } + }, []); + const handleSave = (data: any) => { setSignupData(data); - console.log('현재상태 :', useSignupStore.getState(), '데이터 저장 완료 :', phone, name); }; /** 간편 본인인증 실행 함수 */ @@ -50,7 +77,17 @@ const AuthVerification = () => { async (res: AuthVerificationType) => { try { if (res.success) { - console.log(res); + // 상태 업데이트가 이루어진 후 비동기적으로 reset 호출 + setIsActive(true); + setSignupData({ name: storageName, phone: storagePhone }); + + // 비동기적으로 reset 호출 (setState 후 화면이 렌더링된 후 reset) + setTimeout(() => { + reset({ + name: storageName, + phone: storagePhone, + }); + }, 0); // setState 후 화면 리렌더링을 보장하기 위해 0ms 후 실행 } } catch (err) { console.error(err); @@ -59,21 +96,6 @@ const AuthVerification = () => { ); }; - const { - register, - handleSubmit, - formState: { errors }, - setValue, - } = useForm(); - - /** 페이지가 처음 로드될 때 zustand 상태를 react-hook-form에 반영 */ - useEffect(() => { - setValue('phone', phone); - setValue('name', name); - console.log('useEffect => phone, name:', phone, name); - }, [phone, name, setValue]); - - const ButtonActive = () => (name && phone ? setIsActive(true) : setIsActive(false)); return ( <> @@ -96,11 +118,8 @@ const AuthVerification = () => { { - console.log('이름 변경:', e.target.value); setSignupData({ name: e.target.value }); - register('email').onChange(e); }} placeholder="실명을 입력하세요." register={register('name', { @@ -117,11 +136,8 @@ const AuthVerification = () => { { - console.log('폰번호 변경:', e.target.value); setSignupData({ phone: e.target.value }); - register('email').onChange(e); }} placeholder="‘-’구분없이 입력하세요" onCheck={handleAuth} @@ -145,8 +161,8 @@ const AuthVerification = () => { text="다음" size="large" variant="deepGray" - disabled={true} - active={false} + disabled={false} + active={isActive} />
    From 9257f5710be8b5ecf041ab7d0fe9e8aef9c0e02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=ED=9D=AC=EC=84=A0?= Date: Fri, 17 Jan 2025 17:20:43 +0900 Subject: [PATCH 12/26] =?UTF-8?q?Cont:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/User/SignUp.tsx | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/pages/User/SignUp.tsx b/src/pages/User/SignUp.tsx index 8abbfb7..244ed2d 100644 --- a/src/pages/User/SignUp.tsx +++ b/src/pages/User/SignUp.tsx @@ -7,6 +7,7 @@ import { css } from '@emotion/react'; import { TypoTitleMdSb } from '@styles/Common'; import { useForm } from 'react-hook-form'; import useSignupStore from '@store/useSignupStore'; +import { useState } from 'react'; const SignUp = () => { /** react-hook-form */ @@ -20,11 +21,51 @@ const SignUp = () => { /** zustand 스토어에 데이터 저장 */ // 이후 사용될 Phone, name 추가 호출 필요 const { setSignupData } = useSignupStore(); + const [emailError, setEmailError] = useState(null); const handleVerifyComplete = () => { console.log('본인인증 완료'); }; + const CheckEmail = async (email: string) => { + try { + const response = await fetch(`${import.meta.env.VITE_TOUCHEESE_API}/auth/register/check`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email }), + }); + + if (!response.ok) { + throw new Error('서버 오류가 발생했습니다.'); + } + + const result = await response.json(); + return { success: result.success, message: result.message }; + } catch (error) { + console.error('중복확인 오류:', error); + return { success: false, message: '중복확인 요청에 실패했습니다.' }; + } + }; + + const handleEmailCheck = async () => { + const email = watch('email'); + if (!email) { + setEmailError('이메일을 입력하세요.'); + return; + } + + const { success, message } = await CheckEmail(email); + + if (success) { + setEmailError(null); + alert('사용 가능한 이메일입니다.'); + } else { + setEmailError(message); + } + }; + const onSubmit = (data: any) => { setSignupData(data); }; @@ -74,7 +115,8 @@ const SignUp = () => { message: '올바른 이메일 형식이 아닙니다.', }, })} - error={errors.email?.message?.toString()} + onCheck={handleEmailCheck} + error={emailError || errors.email?.message?.toString()} /> {/* 비밀번호 */} From fd029711c983716f9d74d188e7edf91f5b796cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=ED=9D=AC=EC=84=A0?= Date: Fri, 17 Jan 2025 17:21:16 +0900 Subject: [PATCH 13/26] Done: localStorage -> sessionStorage --- src/store/useSignupStore.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/store/useSignupStore.ts b/src/store/useSignupStore.ts index 05e94e1..f35e2aa 100644 --- a/src/store/useSignupStore.ts +++ b/src/store/useSignupStore.ts @@ -2,8 +2,8 @@ import { create } from 'zustand'; import { createJSONStorage, persist } from 'zustand/middleware'; interface PersistedSignupState { - name: string; - phone: string; + name?: string; + phone?: string; setSignupData: (data: Partial) => void; clearSignupData: () => void; _persist?: { @@ -40,7 +40,7 @@ const useSignupStore = create()( }), { name: 'signup-storage', - storage: createJSONStorage(() => localStorage), + storage: createJSONStorage(() => sessionStorage), }, ), ); From c07dfc504023bf1fa8d8de7617fa36f30418e246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A7=80=EB=AF=BC?= Date: Fri, 17 Jan 2025 17:25:01 +0900 Subject: [PATCH 14/26] =?UTF-8?q?cont:=20=EA=B2=B0=EC=A0=9C=20=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Reservation/ReservationCheck.tsx | 12 +++++++----- src/pages/Reservation/components/Payment.tsx | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pages/Reservation/ReservationCheck.tsx b/src/pages/Reservation/ReservationCheck.tsx index 147ac29..025ddc1 100644 --- a/src/pages/Reservation/ReservationCheck.tsx +++ b/src/pages/Reservation/ReservationCheck.tsx @@ -19,6 +19,7 @@ import Payment from './components/Payment'; import useReservationStore from '@store/useReservationStore'; import { useSelectTimeStore } from '@store/useSelectTimeStore'; import { changeformatDateForUi, useSelectDateStore } from '@store/useSelectDateStore'; +import { useUserStore } from '@store/useUserStore'; interface FormValues { visitorName: string; @@ -39,6 +40,7 @@ const ReservationCheck = () => { const { date } = useSelectDateStore(); const { studioName, totalPrice, options, menuName, basicPrice, menuImage } = useReservationStore(); + const { username, phone } = useUserStore(); const [isDifferentVisitor, setIsDifferentVisitor] = useState(false); const { @@ -117,8 +119,8 @@ const ReservationCheck = () => { {/* 예약자정보 */}

    예약자정보

    -

    박지똥

    -

    010-1234-5678

    +

    {username}

    +

    {phone}

    { setPaymentMethod(e.target.value)} defaultChecked /> @@ -247,7 +249,7 @@ const ReservationCheck = () => { type="radio" name="paymentMethod" onChange={(e) => setPaymentMethod(e.target.value)} - value="naverPay" + value="네이버페이" /> 네이버페이 로고 @@ -256,7 +258,7 @@ const ReservationCheck = () => { type="radio" name="paymentMethod" onChange={(e) => setPaymentMethod(e.target.value)} - value="creditCard" + value="일반신용카드" /> 일반신용카드 diff --git a/src/pages/Reservation/components/Payment.tsx b/src/pages/Reservation/components/Payment.tsx index 992c0fb..f93498e 100644 --- a/src/pages/Reservation/components/Payment.tsx +++ b/src/pages/Reservation/components/Payment.tsx @@ -21,7 +21,7 @@ interface PaymentProps { trigger: () => Promise; paymentMethod: string; isAgreed: boolean; - totalPrice: string; + totalPrice: number; } const Payment = ({ onClick, trigger, paymentMethod, isAgreed, totalPrice }: PaymentProps) => { From 7bd93a8e517e3beba96264c5e90d0a49c41d1258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A7=80=EB=AF=BC?= Date: Fri, 17 Jan 2025 17:25:27 +0900 Subject: [PATCH 15/26] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Reservation/ReservationDetail.tsx | 227 +++++++++++++++++++- 1 file changed, 224 insertions(+), 3 deletions(-) diff --git a/src/pages/Reservation/ReservationDetail.tsx b/src/pages/Reservation/ReservationDetail.tsx index 160bf4b..0bf6b49 100644 --- a/src/pages/Reservation/ReservationDetail.tsx +++ b/src/pages/Reservation/ReservationDetail.tsx @@ -1,9 +1,230 @@ -import { useParams } from 'react-router-dom'; +/** @jsxImportSource @emotion/react */ +import Button from '@components/Button/Button'; +import Header from '@components/Header/Header'; +import { css } from '@emotion/react'; +import useReservationStore from '@store/useReservationStore'; +import { + changeformatDateForUi, + lessThan10Add0, + useSelectDateStore, +} from '@store/useSelectDateStore'; +import { useUserStore } from '@store/useUserStore'; +import { TypoBodySmM, TypoBodySmR, TypoTitleXsSB } from '@styles/Common'; +import variables from '@styles/Variables'; const ReservationDetail = () => { - const { _id } = useParams(); + const { totalPrice, options, menuName, basicPrice } = useReservationStore(); + const { username, phone } = useUserStore(); - return <>{_id} 예약 상세; + // 취소 가능 날짜 계산 + const { date } = useSelectDateStore(); + + const calculateSevenDaysBefore = (date: string): string => { + const targetDate = new Date(date); + targetDate.setDate(targetDate.getDate() - 8); + return `${targetDate.getFullYear()}-${lessThan10Add0(targetDate.getMonth() + 1)}-${lessThan10Add0(targetDate.getDate())}`; + }; + + const getCancellationMessage = (date: string): string => { + const sevenDaysBefore = calculateSevenDaysBefore(date); + const isDeadlinePassed = isPastDeadline(sevenDaysBefore); + + if (isDeadlinePassed) { + return '규정에 따라 예약취소가 불가합니다. 사진관에 문의해주세요.'; + } else { + const formattedDate = changeformatDateForUi({ date: sevenDaysBefore, time: new Set() }); + return `${formattedDate} 23:59까지 예약취소가 가능합니다.`; + } + }; + + const isPastDeadline = (date: string): boolean => { + const deadlineDate = new Date(`${date}T23:59:59`); + const now = new Date(); + return now > deadlineDate; + }; + const sevenDaysBefore = calculateSevenDaysBefore(date); + const isDisabled = isPastDeadline(sevenDaysBefore); + + return ( + <> +
    +
    +
    +

    예약정보

    +
    +
    +
    + 이용 상태 + 사진관에서 예약 확인쭝 +
    +
    + 예약 메뉴 + {menuName} +
    +
    +
    + 추가 옵션 +
    + {options.map((option) => ( + {option.optionName} + ))} +
    +
    +
    +
    +
    +
    +
    +

    예약자정보

    +
    +
    +
    + 이름 + {username} +
    +
    + 전화 번호 + {phone} +
    +
    +
    +
    +
    +

    요청사항

    +
    ㅇㅇ
    +
    +
    +

    결제정보

    +
    +
    + 총 결제금액 + {totalPrice.toLocaleString()}원 +
    +
    + 기본 가격 + {menuName} + {basicPrice?.toLocaleString()}원 +
    +
    + 추가 옵션 + + {options.map((option, index) => ( + + {option.optionName} + {index < options.length - 1 &&
    } +
    + ))} +
    + + {options.map((option, index) => ( + + {option.optionPrice.toLocaleString()}원{index < options.length - 1 &&
    } +
    + ))} +
    +
    +
    + 결제 수단 + 카페 +
    +
    +
    +
    +

    취소/환불 규정

    +
    +

    {getCancellationMessage(date)}

    +
    + 이용 7일 전까지 + 결제 금액에 대한 취소 수수료 없음 +
    +
    + 이용 7일 전 ~ 이용 당일 + 취소 불가 +
    +
    +
    +
    + + ); }; +const containerStyle = css` + display: flex; + flex-direction: column; + gap: 1rem; +`; + +const sectionStyle = css` + margin-bottom: 0.8rem; + + &:last-of-type { + margin-bottom: 0; + } +`; + +const titleStyle = css` + border-bottom: 1px solid ${variables.colors.gray300}; + padding-bottom: 0.8rem; + margin-bottom: 0.8rem; +`; + +const itemStyle = css` + display: flex; + justify-content: space-between; + padding: 0.4rem 0; + font-size: 1.2rem; + + & > span:first-of-type { + ${TypoBodySmR}; + color: ${variables.colors.gray800}; + } + + & > span:nth-of-type(2) { + flex: 1; + margin-left: 1.6rem; + } + + & > span:last-of-type { + text-align: right; + } +`; + +const optionsNameStyle = css` + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.4rem; +`; + +const requestsStyle = css` + ${TypoBodySmR}; + color: ${variables.colors.gray900}; + padding-bottom: 0.8rem; +`; + +const refundInfoRowStyle = css` + ${TypoBodySmR}; + display: flex; + justify-content: space-between; + + span:first-of-type { + color: ${variables.colors.gray800}; + } +`; + +const refundInfoFirstLineStyle = css` + margin: 0.8rem 0; +`; + +const redTextStyle = css` + color: ${variables.colors.red}; +`; export default ReservationDetail; From 42d51fcd2834391491f4a73b702673fd4214336d Mon Sep 17 00:00:00 2001 From: JWJung-99 <39busy@naver.com> Date: Fri, 17 Jan 2025 17:31:08 +0900 Subject: [PATCH 16/26] =?UTF-8?q?Hotfix:=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=ED=83=80=EC=9E=85=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Reservation/ReservationDetail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Reservation/ReservationDetail.tsx b/src/pages/Reservation/ReservationDetail.tsx index 0bf6b49..f411c93 100644 --- a/src/pages/Reservation/ReservationDetail.tsx +++ b/src/pages/Reservation/ReservationDetail.tsx @@ -32,7 +32,7 @@ const ReservationDetail = () => { if (isDeadlinePassed) { return '규정에 따라 예약취소가 불가합니다. 사진관에 문의해주세요.'; } else { - const formattedDate = changeformatDateForUi({ date: sevenDaysBefore, time: new Set() }); + const formattedDate = changeformatDateForUi({ date: sevenDaysBefore, time: [] }); return `${formattedDate} 23:59까지 예약취소가 가능합니다.`; } }; From 5c2004e74f388e9e10c037ce5d7fd1d3733ac6b1 Mon Sep 17 00:00:00 2001 From: JWJung-99 <39busy@naver.com> Date: Fri, 17 Jan 2025 17:38:03 +0900 Subject: [PATCH 17/26] =?UTF-8?q?Hotfix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=B4=ED=9B=84=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/User/KakaoCallback.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/User/KakaoCallback.tsx b/src/pages/User/KakaoCallback.tsx index 94d6050..7e59944 100644 --- a/src/pages/User/KakaoCallback.tsx +++ b/src/pages/User/KakaoCallback.tsx @@ -28,7 +28,7 @@ const KakaoCallback = () => { console.log('result: 필요함', result); if (result.status.length > 1) { - navigate('user/AuthVerification', { + navigate('/user/AuthVerification', { state: { status: result, }, From ff8a51914eb062fb4498fc3977cc7f626dbb0959 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Fri, 17 Jan 2025 17:59:34 +0900 Subject: [PATCH 18/26] =?UTF-8?q?Cont:=20=EC=98=88=EC=95=BD=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20UI=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-calendar-yellow.svg | 3 + .../ReservationCard/ReservationCard.tsx | 147 +++++++++++++++++- src/pages/User/MyPage.tsx | 9 +- 3 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 public/img/icon-calendar-yellow.svg diff --git a/public/img/icon-calendar-yellow.svg b/public/img/icon-calendar-yellow.svg new file mode 100644 index 0000000..f3636a6 --- /dev/null +++ b/public/img/icon-calendar-yellow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx index ab677f9..1f11e10 100644 --- a/src/components/ReservationCard/ReservationCard.tsx +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -1,15 +1,152 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import variables from '@styles/Variables'; +import { useNavigate } from 'react-router-dom'; +import { TypoBodyMdM, TypoBodySmR, TypoTitleXsM } from '@styles/Common'; +import { useState } from 'react'; import StatusChip from './StatusChip'; -const ReservationCard = () => { +const ReservationCard = ({ isMyPage }: { isMyPage?: boolean }) => { + const navigate = useNavigate(); + const [data, setData] = useState(true); + //API 구현시 실제 데이터로 변경 + return ( - <> - 카드 - ; - +
    + {data ? ( +
    + {isMyPage && ( +
    + 일정 d-day 아이콘 +

    방문 1일전

    +
    + )} +
    +
    + +

    + 사진관명 | 메뉴명 +

    +

    2024.12.6 (금) 13:00

    +
    + +
    + 메뉴 사진 +
    +
    +
    + ) : ( +
    +

    + 예약하신 사진관이 없습니다.
    + 매장을 예약하고 인생 사진을 찍어보세요! +

    + +
    + )} +
    ); }; export default ReservationCard; + +const CardStyle = (isMyPage: boolean | undefined, data: boolean) => css` + display: flex; + flex-direction: column; + background-color: ${variables.colors.white}; + border-radius: 0.6rem; + gap: 0.8rem; + ${isMyPage && data + ? `border: 0.1rem solid ${variables.colors.primary600}` + : `border: 0.1rem solid ${variables.colors.gray400}`}; + padding: 1.4rem; +`; + +const ReservationInfoStyle = css` + display: flex; + gap: 1rem; + justify-content: space-between; + align-items: center; + + .cardInfo { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.4rem; + + .cardName { + ${TypoBodySmR} + display: flex; + gap: 0.6rem; + color: ${variables.colors.gray800}; + } + + .cardDate { + ${TypoTitleXsM} + } + } + + .cardCover { + width: 6rem; + aspect-ratio: 60 / 72; + + & img { + object-fit: cover; + } + } +`; + +const AlarmStyle = css` + display: flex; + gap: 0.4rem; + align-items: center; + padding-bottom: 0.8rem; + border-bottom: 0.1rem solid ${variables.colors.gray300}; + + & p { + ${TypoBodyMdM} + } + + & img { + width: 2rem; + height: 2rem; + } +`; + +const EmptyCardStyle = css` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.6rem; + border-radius: 0.6rem; + border: 0.1rem solid ${variables.colors.gray400}; + min-height: 14rem; + text-align: center; + + & p { + color: ${variables.colors.gray700}; + ${TypoBodyMdM} + } + + & button { + display: flex; + gap: 0.4rem; + padding: 0.8rem 1rem; + border-radius: 0.6rem; + background-color: ${variables.colors.primary50}; + border: 0.1rem solid ${variables.colors.primary600}; + + &::before { + content: ''; + width: 1.8rem; + height: 1.8rem; + background-image: url('/img/icon-studio-yellow.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: 1.8rem; + } + } +`; diff --git a/src/pages/User/MyPage.tsx b/src/pages/User/MyPage.tsx index f38cc81..9739aef 100644 --- a/src/pages/User/MyPage.tsx +++ b/src/pages/User/MyPage.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import variables from '@styles/Variables'; import Header from '@components/Header/Header'; -import { Link } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; import { IUser } from 'types/types'; import { defaultUserState } from '@store/useUserStore'; import { getLocalStorageItem } from '@utils/getLocalStorageItem'; @@ -11,6 +11,8 @@ import ReservationCard from '@components/ReservationCard/ReservationCard'; const MyPage = () => { const { username, email } = getLocalStorageItem('userState', defaultUserState); + const { pathname } = useLocation(); + return ( <>
    @@ -19,9 +21,7 @@ const MyPage = () => {

    {email}

    -
    - -
    +
    • @@ -77,6 +77,7 @@ const MyPageMenuStyle = css` height: 1rem; width: calc(100% + (1.6rem * 2)); margin-left: -1.6rem; + margin-top: 1.6rem; } & li { From 309d500c69edd6aff288c4619973d38275f25fd5 Mon Sep 17 00:00:00 2001 From: woojoung Date: Tue, 21 Jan 2025 16:27:53 +0900 Subject: [PATCH 19/26] =?UTF-8?q?Feat:=20=EC=9D=B8=ED=92=8B=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EB=B3=80=EA=B2=BD=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EA=B8=B0=EB=8A=A5=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20+=20valid=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-valid.svg | 3 +++ src/components/Input/Input.tsx | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 public/img/icon-valid.svg diff --git a/public/img/icon-valid.svg b/public/img/icon-valid.svg new file mode 100644 index 0000000..44ea29e --- /dev/null +++ b/public/img/icon-valid.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 9128a57..9b1be0b 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -18,6 +18,7 @@ interface InputProps { onChange?: (e: React.ChangeEvent) => void; checkButtonText?: string; inputWidth?: string; + isValid?: boolean; } const Input = ({ @@ -30,6 +31,7 @@ const Input = ({ onCheck, checkButtonText = '중복확인', inputWidth = '100%', + isValid, }: InputProps) => { const [inputValue, setInputValue] = useState(''); const [showPassword, setShowPassword] = useState(false); @@ -69,7 +71,7 @@ const Input = ({
      )}
      + {isValid && ( +
      + 유효성 검사 통과 +

      유효성 검사 통과

      +
      + )} {error && (
      - error -

      {error}

      + 검증 실패 +

      {error}

      )} @@ -121,7 +129,7 @@ const labelStyle = css` ${TypoBodySmM} `; -const inputStyle = (error?: string) => css` +const inputStyle = (error?: string, isValid?: boolean) => css` && { margin-top: 0.4rem; margin-bottom: 0.4rem; @@ -129,7 +137,7 @@ const inputStyle = (error?: string) => css` height: 5.6rem; box-sizing: border-box; padding: 1rem; - border: 1px solid ${error ? 'red' : variables.colors.gray300}; + border: 1px solid ${error ? 'red' : isValid ? 'green' : variables.colors.gray300}; border-radius: 0.6rem; background-color: ${variables.colors.white}; font-size: 1.6rem; @@ -137,7 +145,7 @@ const inputStyle = (error?: string) => css` ${error && `animation: shake 0.3s ease-in-out 2;`} } &:focus { - outline: 1px solid ${error ? 'red' : variables.colors.primary}; + outline: 1px solid ${error ? 'red' : isValid ? 'green' : variables.colors.primary}; } @keyframes shake { @@ -164,9 +172,8 @@ const errorIconStyle = css` width: 1.6rem; height: 1.6rem; `; - -const errorStyle = css` - color: red; +const errorStyle = (error?: string, isValid?: boolean) => css` + color: ${error ? 'red' : isValid ? 'green' : variables.colors.gray600}; `; const inputWrapperStyle = css` From 881dadd40c292f002d079df2ecd9c5ae1550dd52 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Tue, 21 Jan 2025 22:56:51 +0900 Subject: [PATCH 20/26] =?UTF-8?q?Feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/img/icon-rating-disabled.svg | 3 + public/img/icon-rating.svg | 4 +- .../ReservationCard/RatingReview.tsx | 68 +++++++++++++++++++ .../ReservationCard/ReservationCard.tsx | 12 +++- 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 public/img/icon-rating-disabled.svg create mode 100644 src/components/ReservationCard/RatingReview.tsx diff --git a/public/img/icon-rating-disabled.svg b/public/img/icon-rating-disabled.svg new file mode 100644 index 0000000..f0ec497 --- /dev/null +++ b/public/img/icon-rating-disabled.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icon-rating.svg b/public/img/icon-rating.svg index a6641dc..da66f59 100644 --- a/public/img/icon-rating.svg +++ b/public/img/icon-rating.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/components/ReservationCard/RatingReview.tsx b/src/components/ReservationCard/RatingReview.tsx new file mode 100644 index 0000000..4d0d6dc --- /dev/null +++ b/src/components/ReservationCard/RatingReview.tsx @@ -0,0 +1,68 @@ +/** @jsxImportSource @emotion/react */ +import { css } from '@emotion/react'; +import { TypoCapXsR } from '@styles/Common'; +import variables from '@styles/Variables'; +import { useEffect, useState } from 'react'; + +const RatingReview = ({ ratingValue = 0 }) => { + const [ratingComment, setRatingComment] = useState( + '촬영은 어떠셨나요? 사진관 이용 리뷰를 남겨주세요.', + ); + + useEffect(() => { + switch (ratingValue) { + case 1: + setRatingComment('별로였어요.'); + break; + case 2: + setRatingComment('그저 그랬어요.'); + break; + case 3: + setRatingComment('괜찮았어요.'); + break; + case 4: + setRatingComment('좋았어요.'); + break; + case 5: + setRatingComment('최고였어요!'); + break; + default: + setRatingComment('촬영은 어떠셨나요? 사진관 이용 리뷰를 남겨주세요.'); + } + }, [ratingValue]); + + return ( +
      +

      {ratingComment}

      +
      + {Array.from({ length: 5 }).map((_, i) => ( + {i + ))} +
      +
      + ); +}; + +export default RatingReview; + +const CardRatingStar = css` + display: flex; + flex-direction: column; + gap: 0.6rem; + border-top: 0.1rem solid ${variables.colors.gray300}; + padding-top: 1rem; + + & p { + ${TypoCapXsR} + color: ${variables.colors.gray800}; + } + + .ratingBox { + display: flex; + gap: 0.2rem; + } +`; diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx index 1f11e10..b18132e 100644 --- a/src/components/ReservationCard/ReservationCard.tsx +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -5,8 +5,15 @@ import { useNavigate } from 'react-router-dom'; import { TypoBodyMdM, TypoBodySmR, TypoTitleXsM } from '@styles/Common'; import { useState } from 'react'; import StatusChip from './StatusChip'; - -const ReservationCard = ({ isMyPage }: { isMyPage?: boolean }) => { +import RatingReview from './RatingReview'; + +const ReservationCard = ({ + isMyPage, + isReviewed, +}: { + isMyPage?: boolean; + isReviewed?: boolean; +}) => { const navigate = useNavigate(); const [data, setData] = useState(true); //API 구현시 실제 데이터로 변경 @@ -34,6 +41,7 @@ const ReservationCard = ({ isMyPage }: { isMyPage?: boolean }) => { 메뉴 사진
    + {isReviewed && } ) : (
    From 3c10f41351d76ee5ce96c732585088ef14fa7f39 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Tue, 21 Jan 2025 23:04:40 +0900 Subject: [PATCH 21/26] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=97=90=20=EB=A7=9E=EB=8A=94=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=8F=99=EC=A0=81=20=EB=B3=80=EA=B2=BD=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ReservationCard/StatusChip.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/ReservationCard/StatusChip.tsx b/src/components/ReservationCard/StatusChip.tsx index 9cdced0..583808e 100644 --- a/src/components/ReservationCard/StatusChip.tsx +++ b/src/components/ReservationCard/StatusChip.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/react'; import { TypoCapXsR } from '@styles/Common'; import variables from '@styles/Variables'; +import { useEffect, useState } from 'react'; type ChipType = { state: 'confirmed' | 'pending' | 'completed' | 'canceled'; @@ -9,7 +10,26 @@ type ChipType = { }; const StatusChip = ({ state = 'pending' }: ChipType) => { - return
    이용상태
    ; + const [stateText, setStateText] = useState('pending'); + + useEffect(() => { + switch (state) { + case 'pending': + setStateText('예약확인중'); + break; + case 'confirmed': + setStateText('예약확정'); + break; + case 'completed': + setStateText('이용완료'); + break; + case 'canceled': + setStateText('예약취소'); + break; + } + }, [state]); + + return
    {stateText}
    ; }; export default StatusChip; From 62f877a5fddec45b71e06abee84f2ac0f1802dc1 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Tue, 21 Jan 2025 23:06:00 +0900 Subject: [PATCH 22/26] =?UTF-8?q?Fix=20:=20=ED=8F=89=EC=A0=90=20default=20?= =?UTF-8?q?=EA=B0=92=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ReservationCard/RatingReview.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ReservationCard/RatingReview.tsx b/src/components/ReservationCard/RatingReview.tsx index 4d0d6dc..de26a46 100644 --- a/src/components/ReservationCard/RatingReview.tsx +++ b/src/components/ReservationCard/RatingReview.tsx @@ -26,8 +26,6 @@ const RatingReview = ({ ratingValue = 0 }) => { case 5: setRatingComment('최고였어요!'); break; - default: - setRatingComment('촬영은 어떠셨나요? 사진관 이용 리뷰를 남겨주세요.'); } }, [ratingValue]); From 8d18b6882b4dfc559a2b42026680ced341496987 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Wed, 22 Jan 2025 12:50:28 +0900 Subject: [PATCH 23/26] =?UTF-8?q?Feat:=20=ED=8F=89=EC=A0=90=20prop=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ReservationCard/ReservationCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx index b18132e..9b7d336 100644 --- a/src/components/ReservationCard/ReservationCard.tsx +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -10,9 +10,11 @@ import RatingReview from './RatingReview'; const ReservationCard = ({ isMyPage, isReviewed, + ratingValue = 0, }: { isMyPage?: boolean; isReviewed?: boolean; + ratingValue?: number; }) => { const navigate = useNavigate(); const [data, setData] = useState(true); @@ -41,7 +43,7 @@ const ReservationCard = ({ 메뉴 사진 - {isReviewed && } + {isReviewed && }
    ) : (
    From 4f1732d78e58354ba614851b13053222026ce317 Mon Sep 17 00:00:00 2001 From: kyungmim Date: Wed, 22 Jan 2025 12:52:44 +0900 Subject: [PATCH 24/26] =?UTF-8?q?Type:=20=EC=B9=B4=EB=93=9C=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ReservationCard/ReservationCard.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx index 9b7d336..fa1ac6b 100644 --- a/src/components/ReservationCard/ReservationCard.tsx +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -7,15 +7,13 @@ import { useState } from 'react'; import StatusChip from './StatusChip'; import RatingReview from './RatingReview'; -const ReservationCard = ({ - isMyPage, - isReviewed, - ratingValue = 0, -}: { +type ReservationCardType = { isMyPage?: boolean; - isReviewed?: boolean; + isReview?: boolean; ratingValue?: number; -}) => { +}; + +const ReservationCard = ({ isMyPage, isReview, ratingValue = 0 }: ReservationCardType) => { const navigate = useNavigate(); const [data, setData] = useState(true); //API 구현시 실제 데이터로 변경 @@ -43,7 +41,7 @@ const ReservationCard = ({ 메뉴 사진 - {isReviewed && } + {isReview && }
    ) : (
    From 93501066254ab9bb91ee2f29a9531e47d747144f Mon Sep 17 00:00:00 2001 From: kyungmim Date: Wed, 22 Jan 2025 13:21:28 +0900 Subject: [PATCH 25/26] =?UTF-8?q?Fix:=20=EC=98=88=EC=95=BD=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=EB=B7=B0,=20=EC=83=81=ED=83=9C=EC=B9=A9?= =?UTF-8?q?=20useEffect=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationCard/RatingReview.tsx | 43 +++++++++---------- src/components/ReservationCard/StatusChip.tsx | 32 +++++++------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/components/ReservationCard/RatingReview.tsx b/src/components/ReservationCard/RatingReview.tsx index de26a46..c16e728 100644 --- a/src/components/ReservationCard/RatingReview.tsx +++ b/src/components/ReservationCard/RatingReview.tsx @@ -5,29 +5,28 @@ import variables from '@styles/Variables'; import { useEffect, useState } from 'react'; const RatingReview = ({ ratingValue = 0 }) => { - const [ratingComment, setRatingComment] = useState( - '촬영은 어떠셨나요? 사진관 이용 리뷰를 남겨주세요.', - ); + let ratingComment = ''; - useEffect(() => { - switch (ratingValue) { - case 1: - setRatingComment('별로였어요.'); - break; - case 2: - setRatingComment('그저 그랬어요.'); - break; - case 3: - setRatingComment('괜찮았어요.'); - break; - case 4: - setRatingComment('좋았어요.'); - break; - case 5: - setRatingComment('최고였어요!'); - break; - } - }, [ratingValue]); + switch (ratingValue) { + case 1: + ratingComment = '별로였어요.'; + break; + case 2: + ratingComment = '그저 그랬어요.'; + break; + case 3: + ratingComment = '괜찮았어요.'; + break; + case 4: + ratingComment = '좋았어요.'; + break; + case 5: + ratingComment = '최고였어요!'; + break; + default: + ratingComment = '촬영은 어떠셨나요? 사진관 이용 리뷰를 남겨주세요.'; + break; + } return (
    diff --git a/src/components/ReservationCard/StatusChip.tsx b/src/components/ReservationCard/StatusChip.tsx index 583808e..a335808 100644 --- a/src/components/ReservationCard/StatusChip.tsx +++ b/src/components/ReservationCard/StatusChip.tsx @@ -10,24 +10,22 @@ type ChipType = { }; const StatusChip = ({ state = 'pending' }: ChipType) => { - const [stateText, setStateText] = useState('pending'); + let stateText = ''; - useEffect(() => { - switch (state) { - case 'pending': - setStateText('예약확인중'); - break; - case 'confirmed': - setStateText('예약확정'); - break; - case 'completed': - setStateText('이용완료'); - break; - case 'canceled': - setStateText('예약취소'); - break; - } - }, [state]); + switch (state) { + case 'pending': + stateText = '예약확인중'; + break; + case 'confirmed': + stateText = '예약확정'; + break; + case 'completed': + stateText = '이용완료'; + break; + case 'canceled': + stateText = '예약취소'; + break; + } return
    {stateText}
    ; }; From c24304314df58b79f3c28ef9cc688b3e121b897b Mon Sep 17 00:00:00 2001 From: JWJung-99 <39busy@naver.com> Date: Wed, 22 Jan 2025 14:35:16 +0900 Subject: [PATCH 26/26] =?UTF-8?q?Cont:=20=EC=98=88=EC=95=BD=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A0=81=EC=9A=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationCard/RatingReview.tsx | 1 - .../ReservationCard/ReservationCard.tsx | 28 +++++++------ src/pages/Reservation/ReservationList.tsx | 24 ++++++++--- src/pages/User/MyPage.tsx | 40 ++++++++++++++----- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/components/ReservationCard/RatingReview.tsx b/src/components/ReservationCard/RatingReview.tsx index c16e728..6ca5ffa 100644 --- a/src/components/ReservationCard/RatingReview.tsx +++ b/src/components/ReservationCard/RatingReview.tsx @@ -2,7 +2,6 @@ import { css } from '@emotion/react'; import { TypoCapXsR } from '@styles/Common'; import variables from '@styles/Variables'; -import { useEffect, useState } from 'react'; const RatingReview = ({ ratingValue = 0 }) => { let ratingComment = ''; diff --git a/src/components/ReservationCard/ReservationCard.tsx b/src/components/ReservationCard/ReservationCard.tsx index fa1ac6b..dcc383a 100644 --- a/src/components/ReservationCard/ReservationCard.tsx +++ b/src/components/ReservationCard/ReservationCard.tsx @@ -1,27 +1,27 @@ /** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; +import { convertToDateFormat, getDay } from '@store/useSelectDateStore'; +import { TypoBodyMdM, TypoBodySmR, TypoTitleXsM } from '@styles/Common'; import variables from '@styles/Variables'; import { useNavigate } from 'react-router-dom'; -import { TypoBodyMdM, TypoBodySmR, TypoTitleXsM } from '@styles/Common'; -import { useState } from 'react'; -import StatusChip from './StatusChip'; import RatingReview from './RatingReview'; +import StatusChip from './StatusChip'; +import { IResItem } from '@pages/Reservation/ReservationList'; type ReservationCardType = { isMyPage?: boolean; - isReview?: boolean; - ratingValue?: number; + data: IResItem; }; -const ReservationCard = ({ isMyPage, isReview, ratingValue = 0 }: ReservationCardType) => { +const ReservationCard = ({ isMyPage = false, data }: ReservationCardType) => { const navigate = useNavigate(); - const [data, setData] = useState(true); + // const [data, setData] = useState(true); //API 구현시 실제 데이터로 변경 return (
    {data ? ( -
    +
    {isMyPage && (
    일정 d-day 아이콘 @@ -30,18 +30,20 @@ const ReservationCard = ({ isMyPage, isReview, ratingValue = 0 }: ReservationCar )}
    - +

    - 사진관명 | 메뉴명 + {data.studio} | {data.menu}

    -

    2024.12.6 (금) 13:00

    +

    {`${convertToDateFormat(new Date(data.date))} (${getDay(new Date(data.date))}) ${data.time}`}

    - 메뉴 사진 + 메뉴 사진
    - {isReview && } + {data.status === 'completed' && ( + + )}
    ) : (
    diff --git a/src/pages/Reservation/ReservationList.tsx b/src/pages/Reservation/ReservationList.tsx index b432853..2bb1f91 100644 --- a/src/pages/Reservation/ReservationList.tsx +++ b/src/pages/Reservation/ReservationList.tsx @@ -1,6 +1,7 @@ /** @jsxImportSource @emotion/react */ import Header from '@components/Header/Header'; import ReservationNavigator from '@components/Navigator/ReservationNavigator'; +import ReservationCard from '@components/ReservationCard/ReservationCard'; import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { TypoBodyMdM, TypoTitleXsM } from '@styles/Common'; @@ -10,14 +11,18 @@ import { useEffect, useState } from 'react'; export type ResStatus = '이용 예정' | '이용 완료' | '예약 취소'; const STATUS: ResStatus[] = ['이용 예정', '이용 완료', '예약 취소']; -interface IResItem { +export interface IResItem { id: number; - status: string; + status: 'pending' | 'confirmed' | 'completed' | 'canceled'; studio: string; menu: string; menuImage: string; date: string; time: string; + review?: { + rating: number; + content: string; + }; } // 임시 데이터 @@ -27,7 +32,7 @@ const reserved: IResItem[] = [ status: 'confirmed', studio: '모노 멘션', menu: '상반신 촬영', - menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + menuImage: 'https://i.imgur.com/7C4GSF4.webp', date: '2025-01-25', time: '13:00', }, @@ -36,7 +41,7 @@ const reserved: IResItem[] = [ status: 'pending', studio: '모노 멘션', menu: '상반신 촬영', - menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + menuImage: 'https://i.imgur.com/7C4GSF4.webp', date: '2025-01-18', time: '13:00', }, @@ -48,16 +53,20 @@ const completed: IResItem[] = [ status: 'completed', studio: '모노 멘션', menu: '상반신 촬영', - menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + menuImage: 'https://i.imgur.com/7C4GSF4.webp', date: '2025-01-25', time: '13:00', + review: { + rating: 4, + content: '좋았어요', + }, }, { id: 3, status: 'completed', studio: '모노 멘션', menu: '상반신 촬영', - menuImage: 'https://i.imgur.com/7C4GSF4.jpeg', + menuImage: 'https://i.imgur.com/7C4GSF4.webp', date: '2025-01-12', time: '13:00', }, @@ -117,6 +126,9 @@ const ReservationList = () => { {data.length ? (

    총 {data.length}건

    + {data.map((item) => ( + + ))}
    ) : ( emptyMessage diff --git a/src/pages/User/MyPage.tsx b/src/pages/User/MyPage.tsx index 9739aef..18917c9 100644 --- a/src/pages/User/MyPage.tsx +++ b/src/pages/User/MyPage.tsx @@ -1,18 +1,38 @@ /** @jsxImportSource @emotion/react */ +import Header from '@components/Header/Header'; +import ReservationCard from '@components/ReservationCard/ReservationCard'; import { css } from '@emotion/react'; +import { IResItem } from '@pages/Reservation/ReservationList'; +import { defaultUserState, useUserStore } from '@store/useUserStore'; +import { TypoBodyMdR, TypoTitleMdSb, TypoTitleXsR } from '@styles/Common'; import variables from '@styles/Variables'; -import Header from '@components/Header/Header'; +import { getLocalStorageItem } from '@utils/getLocalStorageItem'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { IUser } from 'types/types'; -import { defaultUserState } from '@store/useUserStore'; -import { getLocalStorageItem } from '@utils/getLocalStorageItem'; -import { TypoBodyMdR, TypoTitleMdSb, TypoTitleXsR } from '@styles/Common'; -import ReservationCard from '@components/ReservationCard/ReservationCard'; const MyPage = () => { const { username, email } = getLocalStorageItem('userState', defaultUserState); const { pathname } = useLocation(); + const data: IResItem = { + id: 2, + status: 'confirmed', + studio: '모노 멘션', + menu: '상반신 촬영', + menuImage: 'https://i.imgur.com/7C4GSF4.webp', + date: '2025-01-25', + time: '13:00', + }; + + // 임시 로그아웃 + const logout = useUserStore((state) => state.resetUser); + const navigate = useNavigate(); + + const handleClick = () => { + logout(); + navigate('/'); + }; + return ( <>
    @@ -21,7 +41,7 @@ const MyPage = () => {

    {email}

    - +
    • @@ -34,6 +54,10 @@ const MyPage = () => { 찜한 사진관
    + + ); }; @@ -124,7 +148,3 @@ const MyPageMenuStyle = css` border-bottom: none; } `; - -const box = css` - box-shadow: inset 0 0 10px black; -`;