From f30d2ef645ab60d15199aa1a61ad818177012a9b Mon Sep 17 00:00:00 2001 From: hamxxn <150767569+hamxxn@users.noreply.github.com> Date: Wed, 20 Aug 2025 21:41:50 +0900 Subject: [PATCH 1/3] feat: voting page --- src/pages/promiseStatus/PromiseStatus.tsx | 8 +- .../promiseStatus/components/Voting.css.ts | 52 +++++++ src/pages/promiseStatus/components/Voting.tsx | 130 +++++++++++++++++- src/pages/promiseStatus/mockUp.ts | 45 ++++++ 4 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 src/pages/promiseStatus/components/Voting.css.ts diff --git a/src/pages/promiseStatus/PromiseStatus.tsx b/src/pages/promiseStatus/PromiseStatus.tsx index 22b0cea..fbe1e47 100644 --- a/src/pages/promiseStatus/PromiseStatus.tsx +++ b/src/pages/promiseStatus/PromiseStatus.tsx @@ -1,8 +1,8 @@ import { useSearchParams } from 'react-router-dom'; -import Pending from './components/Pending'; +import Pending from '@/pages/promiseStatus/components/Pending'; import { PROMISE_STATUS } from '@shared/constant/promiseStatus'; -import Voting from './components/Voting'; -import Confirmed from './components/Confirmed'; +import Voting from '@/pages/promiseStatus/components/Voting'; +import Confirmed from '@/pages/promiseStatus/components/Confirmed'; export default function PromiseStatus() { const [searchParams] = useSearchParams(); @@ -13,7 +13,7 @@ export default function PromiseStatus() { return ( <> {status === PROMISE_STATUS.PENDING && } - {status === PROMISE_STATUS.VOTING && } + {status === PROMISE_STATUS.VOTING && } {status === PROMISE_STATUS.CONFIRMED && } ); diff --git a/src/pages/promiseStatus/components/Voting.css.ts b/src/pages/promiseStatus/components/Voting.css.ts new file mode 100644 index 0000000..7878289 --- /dev/null +++ b/src/pages/promiseStatus/components/Voting.css.ts @@ -0,0 +1,52 @@ +import { vars } from '@shared/styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const votingWrapper = style({ + position: 'relative', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + background: vars.color.blue0, +}); + +export const containerStyle = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '40px', + minHeight: 'calc(100vh - 9.8rem - 8rem)', + background: vars.color.blue0, +}); + +export const votingText = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '1rem', +}); + +export const votingPlaceWrapper = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '2rem', +}); + +export const votingButtonWrapper = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '1rem', + width: '100%', +}); + +export const votingButton = style({ + position: 'sticky', + bottom: '0', + left: '0', + right: '0', + width: '100%', + padding: '1.5rem', +}); diff --git a/src/pages/promiseStatus/components/Voting.tsx b/src/pages/promiseStatus/components/Voting.tsx index 8929525..e64bff8 100644 --- a/src/pages/promiseStatus/components/Voting.tsx +++ b/src/pages/promiseStatus/components/Voting.tsx @@ -1,8 +1,134 @@ +import Header from '@shared/components/header/Header'; +import { IcNavX } from '@svg/index'; +import { useNavigate } from 'react-router-dom'; +import * as styles from '@/pages/promiseStatus/components/Voting.css'; +import Container from '@shared/components/container/Container'; +import Text from '@shared/components/text/Text'; +import { mockUpVoting } from '@/pages/promiseStatus/mockUp'; +import Button from '@shared/components/button/Button'; +import { PROMISE_STATUS_CONFIG } from '@shared/constant/promiseStatus'; +import { BUTTON_VARIANTS } from '@shared/components/button/constant/button'; +import { useState } from 'react'; + interface VotingProps { promiseId: string; isHost: boolean; } export default function Voting({ promiseId, isHost }: VotingProps) { - console.log(promiseId, isHost); - return
; + const navigate = useNavigate(); + const handleClose = () => { + console.log(promiseId); + navigate(`/`); + }; + const { promiseName, promisePlace, promiseAvailableTimes } = mockUpVoting; + const [selectedPlace, setSelectedPlace] = useState< + { + placeName: string; + placeAddress: string; + }[] + >([]); + const [selectedTime, setSelectedTime] = useState< + { + date: string; + startTime: string; + endTime: string; + }[] + >([]); + + const handlePlaceClick = (place: { placeName: string; placeAddress: string }) => { + if (selectedPlace.includes(place)) { + setSelectedPlace(prev => prev.filter(p => p !== place)); + } else { + setSelectedPlace(prev => [...prev, place]); + } + }; + + const handleTimeClick = (time: { date: string; startTime: string; endTime: string }) => { + if (selectedTime.includes(time)) { + setSelectedTime(prev => + prev.filter( + t => t.date !== time.date && t.startTime !== time.startTime && t.endTime !== time.endTime + ) + ); + } else { + setSelectedTime(prev => [...prev, time]); + } + }; + + const handleVote = () => { + console.log(selectedPlace, selectedTime); + handleClose(); + }; + å; + const isFormValid = !isHost && selectedPlace.length > 0 && selectedTime.length > 0; + + return ( +
+
+ +
+ + {promiseName}의 최종 투표를 진행해주세요 + + + 최적의 시간과 장소입니다 + + {isHost && ( + + 약속 생성자는 투표 할 수 없습니다. + + )} +
+ +
+ + 약속 장소를 선택해주세요 + +
+ {promisePlace.map(place => { + const isSelected = selectedPlace.includes(place); + return ( +
+
+
+ + 약속 시간을 선택해주세요 + +
+ {promiseAvailableTimes.map(time => { + const isSelected = selectedTime.includes(time); + return ( +
+
+
+
+
+
+ ); } diff --git a/src/pages/promiseStatus/mockUp.ts b/src/pages/promiseStatus/mockUp.ts index 6e14e87..66ecf48 100644 --- a/src/pages/promiseStatus/mockUp.ts +++ b/src/pages/promiseStatus/mockUp.ts @@ -76,3 +76,48 @@ export const mockUpPlace = { }, ], }; + +export const mockUpVoting = { + promiseName: 'KUIT BARO 2차 회의', + promisePlace: [ + { + placeName: '강남역 스타벅스', + placeAddress: '서울특별시 강남구 역삼동 123-123', + }, + { + placeName: '건국대학교 중앙도서관', + placeAddress: '서울특별시 강남구 역삼동 123-123', + }, + + { + placeName: '강남역 투썸플레이스', + placeAddress: '서울특별시 강남구 역삼동 123-123', + }, + { + placeName: '건국대학교 신공학관', + placeAddress: '서울특별시 강남구 역삼동 123-123', + }, + ], + promiseAvailableTimes: [ + { + date: '2025-04-03', + startTime: '12:00:00', + endTime: '12:30:00', + }, + { + date: '2025-04-03', + startTime: '12:00:00', + endTime: '12:30:00', + }, + { + date: '2025-04-03', + startTime: '12:00:00', + endTime: '12:30:00', + }, + { + date: '2025-04-03', + startTime: '12:00:00', + endTime: '12:30:00', + }, + ], +}; From 651f1d3feb31c830267bde806928f573a9d8a0c8 Mon Sep 17 00:00:00 2001 From: hamxxn <150767569+hamxxn@users.noreply.github.com> Date: Wed, 20 Aug 2025 22:05:19 +0900 Subject: [PATCH 2/3] feat: confirmed page --- src/pages/promiseStatus/PromiseStatus.tsx | 2 +- .../promiseStatus/components/Confirmed.css.ts | 40 ++++++++++ .../promiseStatus/components/Confirmed.tsx | 76 ++++++++++++++++++- src/pages/promiseStatus/components/Voting.tsx | 2 +- src/pages/promiseStatus/mockUp.ts | 15 ++++ 5 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 src/pages/promiseStatus/components/Confirmed.css.ts diff --git a/src/pages/promiseStatus/PromiseStatus.tsx b/src/pages/promiseStatus/PromiseStatus.tsx index fbe1e47..3404ad9 100644 --- a/src/pages/promiseStatus/PromiseStatus.tsx +++ b/src/pages/promiseStatus/PromiseStatus.tsx @@ -14,7 +14,7 @@ export default function PromiseStatus() { <> {status === PROMISE_STATUS.PENDING && } {status === PROMISE_STATUS.VOTING && } - {status === PROMISE_STATUS.CONFIRMED && } + {status === PROMISE_STATUS.CONFIRMED && } ); } diff --git a/src/pages/promiseStatus/components/Confirmed.css.ts b/src/pages/promiseStatus/components/Confirmed.css.ts new file mode 100644 index 0000000..7e03f7f --- /dev/null +++ b/src/pages/promiseStatus/components/Confirmed.css.ts @@ -0,0 +1,40 @@ +import { vars } from '@shared/styles/theme.css'; +import { style } from '@vanilla-extract/css'; + +export const confirmedWrapper = style({ + position: 'relative', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + paddingTop: '9.8rem', + height: '100vh', +}); + +export const containerStyle = style({ + width: '100%', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '40px', +}); + +export const confirmedText = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '1rem', +}); + +export const promiseDetailWrapper = style({ + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +export const confirmedButton = style({ + position: 'fixed', + bottom: '0', + width: '100%', + padding: '1.5rem', +}); diff --git a/src/pages/promiseStatus/components/Confirmed.tsx b/src/pages/promiseStatus/components/Confirmed.tsx index fcd82e8..0af4fe5 100644 --- a/src/pages/promiseStatus/components/Confirmed.tsx +++ b/src/pages/promiseStatus/components/Confirmed.tsx @@ -1,8 +1,76 @@ +import { useNavigate } from 'react-router-dom'; +import Container from '@shared/components/container/Container'; +import Text from '@shared/components/text/Text'; +import { mockUpConfirmed } from '../mockUp'; +import KakaoMap from '@shared/components/kakaoMap/KakaoMap'; +import usePlaceSearch from '@shared/components/kakaoMap/hooks/usePlaceSearch'; +import { useState, useEffect } from 'react'; +import { MAP_SIZE } from '@shared/components/kakaoMap/constant/mapSize'; +import PromiseDetail from '@shared/components/promiseDetail/PromiseDetail'; +import { PROMISE_STATUS, PROMISE_STATUS_CONFIG } from '@shared/constant/promiseStatus'; +import Button from '@shared/components/button/Button'; +import { BUTTON_VARIANTS } from '@shared/components/button/constant/button'; +import * as styles from '@/pages/promiseStatus/components/Confirmed.css'; + interface ConfirmedProps { promiseId: string; - isHost: boolean; } -export default function Confirmed({ promiseId, isHost }: ConfirmedProps) { - console.log(promiseId, isHost); - return
; +export default function Confirmed({ promiseId }: ConfirmedProps) { + const navigate = useNavigate(); + const { promiseName, promisePlace, promiseAvailableTimes, promiseMembersNames } = mockUpConfirmed; + const { searchPlace } = usePlaceSearch(); + const [place, setPlace] = useState<{ lat: number; lng: number } | null>(null); + + useEffect(() => { + searchPlace(promisePlace.placeAddress) + .then(res => res[0]) + .then(placeData => { + if (placeData) { + setPlace({ lat: placeData.lat, lng: placeData.lng }); + } + }) + .catch(error => { + console.error('Failed to search place:', error); + }); + }, [searchPlace, promisePlace.placeAddress]); + + const handleClose = () => { + console.log(promiseId); + navigate(`/`); + }; + + return ( +
+ +
+ + {promiseName} + + + 약속이 확정되었어요 + +
+ +
+ +
+
+
+
+
+ ); } diff --git a/src/pages/promiseStatus/components/Voting.tsx b/src/pages/promiseStatus/components/Voting.tsx index e64bff8..eaaea12 100644 --- a/src/pages/promiseStatus/components/Voting.tsx +++ b/src/pages/promiseStatus/components/Voting.tsx @@ -59,7 +59,7 @@ export default function Voting({ promiseId, isHost }: VotingProps) { console.log(selectedPlace, selectedTime); handleClose(); }; - å; + const isFormValid = !isHost && selectedPlace.length > 0 && selectedTime.length > 0; return ( diff --git a/src/pages/promiseStatus/mockUp.ts b/src/pages/promiseStatus/mockUp.ts index 66ecf48..36b27a4 100644 --- a/src/pages/promiseStatus/mockUp.ts +++ b/src/pages/promiseStatus/mockUp.ts @@ -121,3 +121,18 @@ export const mockUpVoting = { }, ], }; + +export const mockUpConfirmed = { + promiseName: 'KUIT BARO 2차 회의', + promisePlace: { + placeName: '강남역 스타벅스', + placeAddress: '서울특별시 강남구 역삼동 123-123', + }, + + promiseAvailableTimes: { + date: '2025-04-03', + startTime: '12:00:00', + endTime: '12:30:00', + }, + promiseMembersNames: ['John Doe', 'Jane Doe', 'John Smith', 'Jane Smith'], +}; From 848eb91c7940c6041532a02c0b4c64b7b3027cec Mon Sep 17 00:00:00 2001 From: hamxxn <150767569+hamxxn@users.noreply.github.com> Date: Wed, 20 Aug 2025 22:09:48 +0900 Subject: [PATCH 3/3] fix: lint --- src/pages/promiseStatus/PromiseStatus.tsx | 2 +- src/pages/promiseStatus/components/Confirmed.css.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/promiseStatus/PromiseStatus.tsx b/src/pages/promiseStatus/PromiseStatus.tsx index 3404ad9..eda96f6 100644 --- a/src/pages/promiseStatus/PromiseStatus.tsx +++ b/src/pages/promiseStatus/PromiseStatus.tsx @@ -13,7 +13,7 @@ export default function PromiseStatus() { return ( <> {status === PROMISE_STATUS.PENDING && } - {status === PROMISE_STATUS.VOTING && } + {status === PROMISE_STATUS.VOTING && } {status === PROMISE_STATUS.CONFIRMED && } ); diff --git a/src/pages/promiseStatus/components/Confirmed.css.ts b/src/pages/promiseStatus/components/Confirmed.css.ts index 7e03f7f..9365853 100644 --- a/src/pages/promiseStatus/components/Confirmed.css.ts +++ b/src/pages/promiseStatus/components/Confirmed.css.ts @@ -1,4 +1,3 @@ -import { vars } from '@shared/styles/theme.css'; import { style } from '@vanilla-extract/css'; export const confirmedWrapper = style({