Skip to content

Commit 808ae3c

Browse files
authored
Merge pull request #351 from JEOLLOGA/fix/#350/3sp-qa2
[FIX] 3차 스프린트 QA-2
2 parents 5d77c57 + 618cfb2 commit 808ae3c

File tree

25 files changed

+379
-302
lines changed

25 files changed

+379
-302
lines changed

src/app/homePage.css.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ export const modalOverlay = style({
3535
width: '100%',
3636
height: 'calc(100% + 1.2rem)',
3737
marginTop: '-1.2rem',
38-
backgroundColor: theme.COLORS.black60,
3938
justifyContent: 'center',
4039
alignItems: 'center',
41-
zIndex: 1,
40+
zIndex: 100,
4241
});
4342

4443
export const titleWithIconStyle = style({

src/app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import HomeClient from '@app/HomeClient';
22
import RecommendTempleClient from '@app/RecommendTempleClient';
3+
import Icon from '@assets/svgs';
34
import MainBanner from '@components/banner/MainBanner';
45
import DetailTitle from '@components/detailTitle/DetailTitle';
56
import FilterTypeBoxClient from '@components/filter/filterTypeBox/FilterTypeBoxClient';
67
import Footer from '@components/footer/Footer';
78
import Header from '@components/header/Header';
89
import TestBanner from '@components/test/testBanner/TestBanner';
910
import { cookies } from 'next/headers';
11+
import Link from 'next/link';
1012

1113
import * as styles from './homePage.css';
12-
import Link from 'next/link';
13-
import Icon from '@assets/svgs';
1414

1515
const HomePage = async () => {
1616
const cookieStore = await cookies();

src/app/search/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import leftPaddingStyle from './searchPage.css';
88
const SearchPage = () => {
99
return (
1010
<>
11-
<SearchHeader prevPath={'/'} />
11+
<SearchHeader prevPath={'/'} inputAutoFocus={true} />
1212
<div className={leftPaddingStyle}>
1313
<RecentBtnBox />
1414
</div>

src/app/searchResult/searchResultPage.css.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const headerContainer = style({
1717
flexDirection: 'column',
1818
justifyContent: 'center',
1919
alignItems: 'center',
20-
paddingTop: '1rem',
20+
paddingTop: '1.2rem',
2121
marginBottom: '1rem',
2222
zIndex: 3,
2323
});

src/components/banner/MainBanner.tsx

Lines changed: 21 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,181 +1,39 @@
11
'use client';
22

3+
import useInfiniteCarousel from '@hooks/useInfiniteCarousel';
34
import Image from 'next/image';
45
import { useRouter } from 'next/navigation';
5-
import React, { useState, useEffect, useRef, useCallback } from 'react';
6+
import React from 'react';
67

7-
import BANNER_DATA, { BannerItem } from './bannerData';
8+
import BANNER_DATA from './bannerData';
89
import * as styles from './mainBanner.css';
910

10-
type SlideItem = BannerItem & { uniqueKey: string };
11-
1211
const MainBanner = () => {
1312
const router = useRouter();
1413

15-
const slides = [
16-
{ ...BANNER_DATA[BANNER_DATA.length - 1], uniqueKey: 'clone-last' },
17-
...BANNER_DATA.map((item) => ({ ...item, uniqueKey: `real-${item.id}` })),
18-
{ ...BANNER_DATA[0], uniqueKey: 'clone-first' },
19-
];
20-
21-
const totalOriginalSlides = BANNER_DATA.length;
22-
23-
const [currentIndex, setCurrentIndex] = useState(1);
24-
const [isAnimate, setIsAnimate] = useState(true);
25-
const [isDragging, setIsDragging] = useState(false);
26-
const [startX, setStartX] = useState(0);
27-
const [startY, setStartY] = useState(0);
28-
const [currentX, setCurrentX] = useState(0);
29-
30-
const timerRef = useRef<NodeJS.Timeout | null>(null);
31-
32-
const moveNext = useCallback(() => {
33-
setIsAnimate(true);
34-
setCurrentIndex((prev) => prev + 1);
35-
}, []);
36-
37-
const movePrev = useCallback(() => {
38-
setIsAnimate(true);
39-
setCurrentIndex((prev) => prev - 1);
40-
}, []);
41-
42-
const stopAutoSlide = useCallback(() => {
43-
if (timerRef.current) {
44-
clearInterval(timerRef.current);
45-
}
46-
}, []);
47-
48-
const startAutoSlide = useCallback(() => {
49-
stopAutoSlide();
50-
timerRef.current = setInterval(() => {
51-
moveNext();
52-
}, 4000);
53-
}, [moveNext, stopAutoSlide]);
54-
55-
useEffect(() => {
56-
startAutoSlide();
57-
return () => stopAutoSlide();
58-
}, [startAutoSlide, stopAutoSlide, currentIndex]);
59-
60-
const handleTransitionEnd = () => {
61-
if (currentIndex === 0) {
62-
setIsAnimate(false);
63-
setCurrentIndex(totalOriginalSlides);
64-
} else if (currentIndex === slides.length - 1) {
65-
setIsAnimate(false);
66-
setCurrentIndex(1);
67-
}
68-
};
69-
70-
const handleMouseDown = (e: React.MouseEvent) => {
71-
stopAutoSlide();
72-
setIsDragging(true);
73-
setStartX(e.clientX);
74-
setCurrentX(e.clientX);
75-
setIsAnimate(false);
76-
};
77-
78-
const handleMouseMove = (e: React.MouseEvent) => {
79-
if (!isDragging) return;
80-
setCurrentX(e.clientX);
81-
};
82-
83-
const handleMouseUp = () => {
84-
if (!isDragging) return;
85-
setIsDragging(false);
86-
startAutoSlide();
87-
88-
const diff = currentX - startX;
89-
const threshold = 50;
90-
91-
if (diff < -threshold) {
92-
moveNext();
93-
} else if (diff > threshold) {
94-
movePrev();
95-
} else {
96-
setIsAnimate(true);
97-
}
98-
};
99-
100-
const handleMouseLeave = () => {
101-
if (isDragging) {
102-
setIsDragging(false);
103-
setIsAnimate(true);
104-
startAutoSlide();
105-
}
106-
};
107-
108-
const handleBannerClick = (banner: SlideItem) => {
109-
if (Math.abs(currentX - startX) > 5) return;
14+
const {
15+
slides,
16+
currentIndex,
17+
isAnimate,
18+
dragOffset,
19+
displayIndex,
20+
totalOriginalSlides,
21+
isSwiped,
22+
handlers,
23+
} = useInfiniteCarousel({
24+
data: BANNER_DATA,
25+
autoPlayInterval: 4000,
26+
});
27+
28+
const handleBannerClick = (banner: (typeof BANNER_DATA)[0]) => {
29+
if (isSwiped) return;
11030

11131
if (banner.type === 'internal') {
11232
router.push(banner.link);
11333
} else {
11434
window.open(banner.link, '_blank');
11535
}
11636
};
117-
118-
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>, banner: SlideItem) => {
119-
if (e.key === 'Enter' || e.key === ' ') {
120-
handleBannerClick(banner);
121-
}
122-
};
123-
124-
const handleTouchStart = (e: React.TouchEvent) => {
125-
stopAutoSlide();
126-
setIsDragging(true);
127-
const touchX = e.touches[0].clientX;
128-
const touchY = e.touches[0].clientY;
129-
setStartX(touchX);
130-
setStartY(touchY);
131-
setCurrentX(touchX);
132-
setIsAnimate(false);
133-
};
134-
135-
const handleTouchMove = (e: React.TouchEvent) => {
136-
if (!isDragging) return;
137-
const touchX = e.touches[0].clientX;
138-
const touchY = e.touches[0].clientY;
139-
140-
const diffX = Math.abs(touchX - startX);
141-
const diffY = Math.abs(touchY - startY);
142-
143-
// 스크롤 의도 파악
144-
if (diffY > diffX && diffY > 10) {
145-
setIsDragging(false);
146-
return;
147-
}
148-
149-
setCurrentX(touchX);
150-
};
151-
152-
const handleTouchEnd = () => {
153-
if (!isDragging) {
154-
startAutoSlide();
155-
return;
156-
}
157-
158-
setIsDragging(false);
159-
startAutoSlide();
160-
161-
const diff = currentX - startX;
162-
const threshold = 50;
163-
164-
if (diff < -threshold) {
165-
moveNext();
166-
} else if (diff > threshold) {
167-
movePrev();
168-
} else {
169-
setIsAnimate(true);
170-
}
171-
};
172-
173-
let displayIndex = currentIndex;
174-
if (currentIndex === 0) displayIndex = totalOriginalSlides;
175-
else if (currentIndex === slides.length - 1) displayIndex = 1;
176-
177-
const dragOffset = isDragging ? currentX - startX : 0;
178-
17937
return (
18038
<div className={styles.container}>
18139
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
@@ -186,22 +44,15 @@ const MainBanner = () => {
18644
transition: isAnimate ? 'transform 0.5s ease-in-out' : 'none',
18745
touchAction: 'pan-y',
18846
}}
189-
onTransitionEnd={handleTransitionEnd}
190-
onMouseDown={handleMouseDown}
191-
onMouseMove={handleMouseMove}
192-
onMouseUp={handleMouseUp}
193-
onMouseLeave={handleMouseLeave}
194-
onTouchStart={handleTouchStart}
195-
onTouchMove={handleTouchMove}
196-
onTouchEnd={handleTouchEnd}>
47+
{...handlers}>
19748
{slides.map((banner) => (
19849
<div
19950
key={banner.uniqueKey}
20051
className={styles.slideItem}
20152
onClick={() => handleBannerClick(banner)}
20253
role="button"
20354
tabIndex={0}
204-
onKeyDown={(e) => handleKeyDown(e, banner)}
55+
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && handleBannerClick(banner)}
20556
aria-label={`${banner.alt} 배너로 이동`}
20657
onDragStart={(e) => e.preventDefault()}>
20758
<Image

src/components/banner/mainBanner.css.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const container = style({
77
overflow: 'hidden',
88
aspectRatio: '375 / 347',
99
marginBottom: '40px',
10+
touchAction: 'pan-y',
1011
});
1112

1213
export const slideList = style({

src/components/card/popularCard/PopularCard.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use client';
22

33
import Icon from '@assets/svgs';
4-
import RankBtn from '@components/card/popularCard/RankBtn';
54
import Image from 'next/image';
6-
75
import * as styles from './popularCard.css';
6+
import RankBtn from '@components/card/popularCard/RankBtn';
87

98
interface PopularCardProps {
109
ranking: number;
@@ -15,8 +14,8 @@ interface PopularCardProps {
1514
isLiked: boolean;
1615
onLikeToggle: (templestayId: number) => void;
1716
templestayId: number;
18-
link: string;
1917
onClick: () => void;
18+
priority?: boolean;
2019
}
2120

2221
const PopularCard = ({
@@ -28,8 +27,8 @@ const PopularCard = ({
2827
isLiked,
2928
onLikeToggle,
3029
templestayId,
31-
link,
3230
onClick,
31+
priority = false,
3332
}: PopularCardProps) => {
3433
const handleLikeClick = (e: React.MouseEvent) => {
3534
e.stopPropagation();
@@ -39,22 +38,31 @@ const PopularCard = ({
3938
};
4039

4140
return (
42-
<a
43-
href={link}
44-
className={styles.cardWrapper}
45-
draggable={false}
46-
onDragStart={(e) => e.preventDefault()}
47-
onClick={onClick}>
48-
<div>
49-
<div className={styles.imgBox}>
41+
<div
42+
className={styles.slideItem}
43+
onClick={onClick}
44+
role="button"
45+
tabIndex={0}
46+
onKeyDown={(e) => {
47+
if (e.key === 'Enter' || e.key === ' ') {
48+
e.preventDefault();
49+
onClick();
50+
}
51+
}}
52+
aria-label={`${templestayName} 로 이동`}
53+
onDragStart={(e) => e.preventDefault()}>
54+
<div className={styles.slideContent}>
55+
<div className={styles.imageWrapper}>
5056
<Image
5157
src={templeImg}
5258
alt={`${templestayName} 대표 이미지`}
5359
fill
54-
style={{ objectFit: 'cover' }}
60+
style={{ objectFit: 'cover', objectPosition: 'center' }}
61+
priority={priority}
5562
/>
5663
<RankBtn ranking={ranking} />
5764
</div>
65+
5866
<div className={styles.bottomWrapper}>
5967
<div className={styles.bottomContainer}>
6068
<h3 className={styles.templestayName}>{templestayName}</h3>
@@ -64,12 +72,13 @@ const PopularCard = ({
6472
<span>{templeName}</span>
6573
</div>
6674
</div>
75+
6776
<button className={styles.likeBtn} onClick={handleLikeClick}>
6877
{isLiked ? <Icon.IcnFlowerPink /> : <Icon.IcnFlowerGray />}
6978
</button>
7079
</div>
7180
</div>
72-
</a>
81+
</div>
7382
);
7483
};
7584

0 commit comments

Comments
 (0)