Skip to content

Commit 4989c72

Browse files
authored
Merge pull request #130 from geulDa/fix/#119/qa-refactor
fix&refactor: 로딩 상태 스켈레톤 ui
2 parents d226a85 + bb5d85b commit 4989c72

File tree

2 files changed

+78
-35
lines changed

2 files changed

+78
-35
lines changed

src/pages/main/node/[placeId].tsx

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client';
2-
import { useState } from 'react';
2+
import { useState, useEffect } from 'react';
33
import Image from 'next/image';
44
import { useRouter } from 'next/router';
55
import {
@@ -15,6 +15,7 @@ import { useGetPlaceDetail } from '@/shared/main/queries/useGetPlaceDetail';
1515
import { useUserStatus } from '@/shared/hooks/useUserStatus';
1616
import { useStampAcquire } from '@/shared/api/main/node/queries/useStampAcquire';
1717
import { savePostcard } from '@/shared/utils/storage';
18+
import { Skeleton } from '@/shared/components/skeleton/Skeleton';
1819

1920
const Node = () => {
2021
const router = useRouter();
@@ -23,6 +24,12 @@ const Node = () => {
2324
const [showErrorPopup, setShowErrorPopup] = useState(false);
2425
const { isLoggedIn } = useUserStatus();
2526

27+
// 이미지 로딩 상태
28+
const [imageLoaded, setImageLoaded] = useState(false);
29+
30+
// 스켈레톤 표시 여부 (로딩이 1초 이상일 때만 true)
31+
const [showSkeleton, setShowSkeleton] = useState(false);
32+
2633
// 스탬프 획득 훅
2734
const { mutate: acquireStamp } = useStampAcquire();
2835

@@ -31,13 +38,35 @@ const Node = () => {
3138
router.isReady ? Number(placeId) : undefined,
3239
);
3340

34-
if (isLoading) return <p className='text-center mt-10'>로딩 중...</p>;
41+
useEffect(() => {
42+
let timer: NodeJS.Timeout;
43+
if (isLoading) {
44+
timer = setTimeout(() => setShowSkeleton(true), 1000);
45+
} else {
46+
setShowSkeleton(false);
47+
}
48+
return () => clearTimeout(timer);
49+
}, [isLoading]);
50+
51+
if (isLoading && showSkeleton) {
52+
return (
53+
<div className='flex flex-col items-center justify-center px-[2.4rem] mt-10'>
54+
<Header title='로딩중.. ' onClick={() => router.back()} />
55+
<div className='mt-[10rem] flex flex-col gap-[1.2rem] w-full'>
56+
<Skeleton className='w-full max-w-[354px] h-[300px] rounded-[16px]' />
57+
<Skeleton className='w-full max-w-[354px] h-[100px] rounded-[16px]' />
58+
<Skeleton className='w-full max-w-[354px] h-[50px] rounded-[16px]' />
59+
</div>
60+
</div>
61+
);
62+
}
63+
3564
if (isError || !data)
3665
return <p className='text-center mt-10'>데이터를 불러오지 못했습니다 😢</p>;
3766

3867
const { isCompleted, imageUrl, placeName, description, address } = data.data;
3968

40-
// 스탬프 찍기 버튼 클릭 핸들러
69+
// 🔹 스탬프 찍기 버튼
4170
const handleStampClick = () => {
4271
if (!isLoggedIn) {
4372
setShowLoginPopup(true);
@@ -46,21 +75,17 @@ const Node = () => {
4675

4776
if (isCompleted) return;
4877

49-
// 위치 가져와서 API 호출
5078
getLocation(
5179
(pos) => {
5280
const body = {
53-
// 하드 코딩
5481
latitude: 37.48585193654532,
5582
longitude: 126.80355242431538,
56-
// 실제
83+
// 실제 위치 사용 시:
5784
// latitude: pos.coords.latitude,
5885
// longitude: pos.coords.longitude,
5986
};
6087
const placeIdNum = Number(placeId);
6188

62-
console.log('📍 현재 위치:', body);
63-
6489
acquireStamp(
6590
{ placeId: placeIdNum, body },
6691
{
@@ -95,33 +120,45 @@ const Node = () => {
95120
role='main'
96121
aria-label={`${placeName} 상세 페이지`}
97122
>
98-
<section className='relative w-full'>
99-
<Image
100-
src={imageUrl || '/assets/board.svg'}
101-
alt={placeName}
102-
width={354}
103-
height={436}
104-
className={cn(
105-
'w-full h-auto object-cover block rounded-[16px] transition-all duration-300',
106-
!isCompleted && 'blur-xs brightness-90',
123+
<section className='relative w-full h-[256px]'>
124+
<div className='relative w-full h-full rounded-[16px] overflow-hidden'>
125+
{!imageLoaded && (
126+
<Skeleton className='absolute inset-0 w-full h-full rounded-[16px] animate-pulse bg-gradient-to-br from-gray-200 to-gray-100' />
107127
)}
108-
/>
109128

110-
<button
111-
aria-label={isCompleted ? '스탬프 획득 완료' : '스탬프 찍기'}
112-
className={cn(
113-
'absolute bottom-0 right-0',
114-
isCompleted && 'p-[2.5rem]',
115-
)}
116-
onClick={handleStampClick}
117-
>
118-
<Icon
119-
name={isCompleted ? 'Stamp' : 'PressStamp'}
120-
color={isCompleted ? 'pink-400' : 'gray-50'}
121-
size={isCompleted ? 100 : 160}
122-
aria-hidden='true'
129+
<Image
130+
src={imageUrl || '/assets/board.svg'}
131+
alt={placeName}
132+
fill
133+
sizes='(max-width: 768px) 100vw, 354px'
134+
priority={false}
135+
onLoadingComplete={() => setImageLoaded(true)}
136+
className={cn(
137+
'object-cover rounded-[16px] transition-opacity duration-500',
138+
!isCompleted && 'blur-xs brightness-90',
139+
imageLoaded ? 'opacity-100' : 'opacity-0',
140+
)}
123141
/>
124-
</button>
142+
</div>
143+
144+
{imageLoaded && (
145+
<button
146+
aria-label={isCompleted ? '스탬프 획득 완료' : '스탬프 찍기'}
147+
className={cn(
148+
'absolute bottom-0 right-0',
149+
isCompleted && 'p-[2.5rem]',
150+
imageLoaded ? 'opacity-100' : 'opacity-0 h-0',
151+
)}
152+
onClick={handleStampClick}
153+
>
154+
<Icon
155+
name={isCompleted ? 'Stamp' : 'PressStamp'}
156+
color={isCompleted ? 'pink-400' : 'gray-50'}
157+
size={isCompleted ? 100 : 160}
158+
aria-hidden='true'
159+
/>
160+
</button>
161+
)}
125162
</section>
126163

127164
<LocationCard
@@ -131,11 +168,10 @@ const Node = () => {
131168
variant='mint'
132169
size='large'
133170
/>
134-
135171
<AddressCopy variant='mint' value={address} />
136172
</main>
137173

138-
{/* 로그인 필요 팝업 */}
174+
{/* 팝업 영역 */}
139175
{showLoginPopup && (
140176
<PopupSet
141177
text='로그인이 필요한 서비스입니다.'
@@ -146,7 +182,6 @@ const Node = () => {
146182
/>
147183
)}
148184

149-
{/* 위치 에러 팝업 */}
150185
{showErrorPopup && (
151186
<PopupSet
152187
text='해당 위치를 다시 확인해 주세요.'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// src/shared/components/ui/Skeleton.tsx
2+
import { cn } from '@/shared/lib';
3+
4+
export const Skeleton = ({ className }: { className?: string }) => {
5+
return (
6+
<div className={cn('animate-pulse bg-gray-200 rounded-md', className)} />
7+
);
8+
};

0 commit comments

Comments
 (0)