Skip to content

Commit 6db1536

Browse files
dkslel1225jyn
andauthored
[♻️Refactor] 9/3 QA 피드백 반영 (#212)
* 🎨 style: 후기, 카카오맵에 스켈레톤UI 적용 * 🐛 fix: 체험 상세 페이지 방문 후 뒤로가기 시 페이지 번호 유지 - 뒤로가기로 메인 페이지가 마운트될 때 URL 파라미터를 확인하여 페이지 상태값에 반영 → 사용자가 보던 페이지 번호를 그대로 유지 - 헤더 로고 클릭 시 1페이지로 리셋 * ✨feat : 예약현황 모달 바깥 클릭 시 모달 닫힘 * 🎨 style: 카카오맵 맵 마커 스타일 커스텀 * 🐛 ✨ fix: 예약신청 성공 후 화면 새로고침 -> 관련 캐시 업대이트 * ✨ feat: 주소 복사 기능 * Update README.md --------- Co-authored-by: jyn <spdlqjdkslel1226@naver.com>
1 parent 7c33a91 commit 6db1536

9 files changed

Lines changed: 109 additions & 33 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
<div align="center">
2222
<details>
23-
<summary> 👀 Preview Image</summary>
23+
<summary> 👀 Preview GIF/MP4</summary>
2424

2525
#### 회원가입/로그인
2626
![회원가입_로그인_2배속](https://github.com/user-attachments/assets/27669840-8ad0-4cfc-895b-0731aa1e2e4e)
@@ -34,7 +34,8 @@
3434
---
3535

3636
#### 체험 등록
37-
![체험등록](https://github.com/user-attachments/assets/faa8d167-edcf-4e7b-865f-202c49fa1bf2)
37+
38+
https://github.com/user-attachments/assets/e7392d86-01c6-4a3e-9dea-505839d868ca
3839

3940
#### 마이페이지 내 체험 관리 - 체험 수정
4041

@@ -55,6 +56,8 @@
5556

5657
#### 상세페이지
5758

59+
https://github.com/user-attachments/assets/1dcba629-9388-41bc-a9a5-a5c0f5c12bc1
60+
5861
---
5962

6063
</details>

src/app/my/reserve-calendar/page.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Dropdown from '@/shared/components/dropdown/dropdown';
1818
import AdaptiveModal from '@/shared/components/modal/components/adaptive-modal/adaptive-modal';
1919
import { useModalStore } from '@/shared/components/modal/libs/stores/useModalStore';
2020
import NoData from '@/shared/components/no-data/no-data';
21+
import { cn } from '@/shared/libs/cn';
2122
import useWindowSize from '@/shared/libs/hooks/useWindowSize';
2223
import { Activity } from '@/shared/types/activity';
2324

@@ -257,8 +258,22 @@ const ReserveCalendarPage = () => {
257258
onCellClick={handleDateClick}
258259
calendarRef={calendarRef}
259260
>
261+
{/* 모달 투명 오버레이 - 모달 바깥 클릭 -> closeModal */}
262+
{isDesktop && selectedDate && (
263+
<div
264+
className="fixed inset-0 z-80"
265+
onClick={() => {
266+
resetSelectedDate();
267+
resetDate();
268+
}}
269+
role="presentation"
270+
></div>
271+
)}
260272
{/* 모달 위치 제어하기 위한 div 태그 */}
261-
<div style={modalPosition}>
273+
<div
274+
style={modalPosition}
275+
className={cn(isDesktop && selectedDate && 'z-90')}
276+
>
262277
{/* 예약 현황 모달 */}
263278
<AdaptiveModal extraClassName="shadow-experience-card category-scroll h-[50.8rem] overflow-scroll border border-gray-50 md:h-[39.7rem] lg:h-[44.4rem] lg:w-[32.3rem]">
264279
<div className="p-4">

src/features/activities/components/all-activities.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import { ChevronDown } from 'lucide-react';
44
import Image from 'next/image';
5-
import { useState } from 'react';
5+
import { useSearchParams } from 'next/navigation';
6+
import { useEffect, useState } from 'react';
67

78
import { ActivityCard } from '@/features/activities/components/activity-card';
89
import useResActivitiesQuery from '@/features/activities/libs/hooks/useResActivitiesQuery';
@@ -63,6 +64,25 @@ const AllActivities = ({ keyword }: AllActivitiesProps) => {
6364
setPage(1);
6465
};
6566

67+
// 첫마운트 시 - 파라미터 반영하여 페이지 상태값 재설정
68+
const pageParams = useSearchParams().get('page');
69+
useEffect(() => {
70+
if (pageParams) {
71+
setPage(Number(pageParams)); // * 값이 이전과 동일하면 리렌더X
72+
}
73+
}, [pageParams]);
74+
75+
// page 변경 시, page 파라미터 수정 (* URL변경, 리렌더 트리거X)
76+
useEffect(() => {
77+
const params = new URLSearchParams(window.location.search);
78+
params.set('page', String(page));
79+
window.history.replaceState(
80+
{},
81+
'',
82+
`${window.location.pathname}?${params.toString()}`,
83+
);
84+
}, [page]);
85+
6686
return (
6787
<div className="px-[2.4rem] md:px-[3rem] lg:px-[4rem]">
6888
<div className="mb-[1rem] flex items-center justify-between md:mb-[1.6rem] lg:mb-[2rem]">

src/features/activityId/components/map/address-with-map.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
1+
'use client';
2+
import { Copy } from 'lucide-react';
3+
import { useRef } from 'react';
4+
15
import KakaoMap from '@/features/activityId/components/map/kakao-map';
26
import { textStyle } from '@/features/activityId/libs/constants/variants';
37

48
const AddressWithMap = ({ address }: { address: string | undefined }) => {
9+
const textRef = useRef<HTMLParagraphElement>(null);
10+
11+
const handleCopy = async () => {
12+
if (textRef.current) {
13+
const text = textRef.current.innerText;
14+
await navigator.clipboard.writeText(text);
15+
}
16+
};
17+
518
return (
619
<>
720
<section className="flex flex-col gap-[0.8rem]">
821
<h2 className={textStyle.h2}>오시는 길</h2>
9-
<p className="text-[1.4rem] font-semibold text-gray-800">{address}</p>
22+
<div className="flex items-center gap-1">
23+
<p
24+
ref={textRef}
25+
className="text-[1.4rem] font-semibold text-gray-800"
26+
>
27+
{address}
28+
</p>
29+
<button
30+
onClick={handleCopy}
31+
className="flex-center trans-colors-200 cursor-pointer gap-1 rounded-2xl p-2.5 text-[1.4rem] text-gray-700 select-none hover:bg-gray-50 active:bg-gray-100"
32+
>
33+
<Copy strokeWidth={1.5} className="size-[1.8rem]" />
34+
복사
35+
</button>
36+
</div>
1037
<div
1138
id="map"
1239
className="mb-[2rem] h-[18rem] w-full overflow-hidden rounded-[1.6rem] md:h-[38rem] lg:mb-[4rem] lg:h-[45rem] lg:rounded-[2.4rem]"

src/features/activityId/components/map/kakao-map.tsx

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
import Image from 'next/image';
44
import { useEffect, useState } from 'react';
5-
import { Map, MapMarker, useKakaoLoader } from 'react-kakao-maps-sdk';
5+
import { CustomOverlayMap, Map, useKakaoLoader } from 'react-kakao-maps-sdk';
66

77
import { ErrorMessage } from '@/shared/components/error-message/error-message';
8-
import LoadingSpinner from '@/shared/components/loading-spinner/loading-spinner';
8+
import { DetailKakaoMapSkeleton } from '@/shared/components/skeleton/skeleton';
99

1010
//현재 로컬에 대한 키를 발급 받은거라, 머지할때는 배포 주소로 다시 받아야 함
1111
const KakaoMap = ({ address }: { address: string | undefined }) => {
12-
const [coordinates, setCoordinates] = useState<
13-
{ lat: number; lng: number } | { x: number; y: number }
14-
>();
12+
const [placeName, setPlaceName] = useState<string | null>(null);
13+
const [coordinates, setCoordinates] = useState<{
14+
lat: number;
15+
lng: number;
16+
}>();
1517

1618
const [loading, error] = useKakaoLoader({
1719
appkey: process.env.NEXT_PUBLIC_KAKAO!,
@@ -42,16 +44,23 @@ const KakaoMap = ({ address }: { address: string | undefined }) => {
4244
const geocoder = new kakao.maps.services.Geocoder();
4345
geocoder.addressSearch(address, (result, status) => {
4446
if (status === kakao.maps.services.Status.OK) {
45-
const { x, y } = result[0];
47+
const { x, y, road_address, address_name } = result[0];
4648
setCoordinates({ lat: parseFloat(y), lng: parseFloat(x) });
49+
50+
// 건물 이름 우선, 없으면 전체 주소 사용
51+
if (road_address?.building_name) {
52+
setPlaceName(road_address.building_name);
53+
} else {
54+
setPlaceName(address_name);
55+
}
4756
}
4857
});
4958
};
5059

5160
tryGeocode();
5261
}, [loading, error, address]);
5362

54-
if (loading) return <LoadingSpinner />;
63+
if (loading) return <DetailKakaoMapSkeleton />;
5564
if (error) return <ErrorMessage className="h-full" />;
5665
if (coordinates) {
5766
return (
@@ -61,22 +70,27 @@ const KakaoMap = ({ address }: { address: string | undefined }) => {
6170
center={coordinates}
6271
style={{ width: '100%', height: '100%' }}
6372
>
64-
<MapMarker position={coordinates}></MapMarker>
73+
<CustomOverlayMap position={coordinates}>
74+
<div className="border-sub-300 flex-center relative gap-[0.6rem] rounded-full border-2 bg-white p-3 text-[1.4rem] font-semibold text-gray-950 shadow-lg">
75+
<Image
76+
src={'/images/icons/profile-default.svg'}
77+
alt="map-icon"
78+
width={30}
79+
height={30}
80+
className="shrink-0"
81+
/>
82+
<p className="shrink-0 pr-[0.2rem]">{placeName}</p>
83+
</div>
84+
</CustomOverlayMap>
6585
</Map>
6686
</>
6787
);
6888
} else {
6989
return (
70-
<div className="flex-center inset-0 flex-col gap-[1rem] md:gap-[4rem]">
71-
<div className="mt-[0.6rem] text-[1.4rem] text-gray-500 md:mt-[8rem] md:text-[1.6rem] lg:mt-[12rem] lg:text-3xl">
72-
해당 주소를 찾을 수 없습니다
90+
<div className="flex-center size-full">
91+
<div className="text-[1.4rem] text-gray-500 md:text-[1.8rem]">
92+
지도를 찾고 있습니다.
7393
</div>
74-
<Image
75-
src="/images/sad-laptop.svg"
76-
width={200}
77-
height={100}
78-
alt="failed-load-map"
79-
/>
8094
</div>
8195
);
8296
}

src/features/activityId/components/reservation-form/reservation-form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const ReservationForm = ({
9292
setSelectedTime('');
9393
reset(); // 제출 후 폼 초기화
9494
submittingRef.current = false;
95+
window.location.reload();
9596
},
9697
onError: () => {
9798
submittingRef.current = false;

src/features/activityId/components/reviews.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import { useState } from 'react';
33

44
import { useReviewsQuery } from '@/features/activityId/libs/hooks/useReviewsQuery';
5-
import { ErrorMessage, LoadingSpinner } from '@/shared/components';
5+
import { ErrorMessage } from '@/shared/components';
66
import Pagination from '@/shared/components/pagination/pagination';
7+
import { DetailReviewsSkeleton } from '@/shared/components/skeleton/skeleton';
78
import StarImage from '@/shared/components/star/star';
89
import { cn } from '@/shared/libs/cn';
910

@@ -17,12 +18,7 @@ const Reviews = ({ activityId }: { activityId: number }) => {
1718
});
1819
const isPageNecessary = data && data.totalCount > 0;
1920

20-
if (isLoading)
21-
return (
22-
<div className="shadow-experience-card mb-[1.6rem] h-[11rem] w-full md:h-[11.3rem]">
23-
<LoadingSpinner />
24-
</div>
25-
);
21+
if (isLoading) return <DetailReviewsSkeleton />;
2622
if (isError) return <ErrorMessage />;
2723
return (
2824
<>

src/shared/components/header/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const Header = () => {
5050
onClick={(e) => {
5151
e.preventDefault();
5252
resetSearch();
53-
router.push('/activities');
53+
router.push('/activities?page=1');
5454
}}
5555
>
5656
<Image

src/shared/components/skeleton/skeleton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export const DetailSubImagesSkeleton = () => {
144144
// 상세 페이지 카카오 맵 스켈레톤
145145
export const DetailKakaoMapSkeleton = () => {
146146
return (
147-
<div className="shadow-experience-card h-[18rem] w-[32.7rem] animate-pulse rounded-[1.6rem] bg-gray-200 md:h-[38rem] md:w-[68.8rem] lg:h-[45rem] lg:w-[66.1rem]" />
147+
<div className="shadow-experience-card h-[18rem] w-full animate-pulse rounded-[1.6rem] bg-gray-200 md:h-[38rem] lg:h-[45rem]" />
148148
);
149149
};
150150

@@ -153,11 +153,11 @@ export const DetailReviewsSkeleton = () => {
153153
return (
154154
<div className="animate-pulse">
155155
{/* 후기 리스트 */}
156-
<ul className="space-y-[4rem]">
156+
<ul className="mb-[11.5rem] space-y-[4rem] lg:mb-[21rem]">
157157
{Array.from({ length: 3 }, (_, index) => (
158158
<li key={`review-skeleton-${index}`}>
159159
<article
160-
className={`shadow-experience-card h-[11rem] rounded-[2.4rem] bg-gray-200 p-[2rem] md:h-[11.3rem] ${index === 2 ? 'mb-[3rem]' : 'mb-[4rem]'}`}
160+
className={`shadow-experience-card h-[11rem] rounded-[2.4rem] bg-gray-200 p-[2rem] md:h-[11.3rem]`}
161161
></article>
162162
</li>
163163
))}

0 commit comments

Comments
 (0)