Skip to content

Commit eceab37

Browse files
authored
Fix/121 QA(1,2차 통합) 후 나온 메인페이지, 체험 등록/수정 페이지 이슈 해결
Fix/121 QA(1,2차 통합) 후 나온 메인페이지, 체험 등록/수정 페이지 이슈 해결
2 parents 75c3ed9 + 95fab3a commit eceab37

22 files changed

Lines changed: 290 additions & 106 deletions

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/(with-header)/components/BannerSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function BannerSection({ keyword }: BannerSectionProps) {
2222
<div className='absolute inset-0 bg-gradient-to-r from-black to-transparent' />
2323

2424
{/* 텍스트 콘텐츠 */}
25-
<div className='relative z-10 flex flex-col items-start w-220 max-w-1200 md:w-440 lg:w-full pl-24 pt-74 md:pl-32 lg:pl-0 md:pt-144 lg:pt-159 lg:ml-auto lg:mr-auto gap-8 lg:gap-20 h-full text-white font-bold break-keep'>
25+
<div className='relative z-10 flex flex-col items-start max-w-1200 md:w-440 lg:w-full pl-24 pt-74 md:pl-32 lg:pl-0 md:pt-144 lg:pt-159 lg:ml-auto lg:mr-auto gap-8 lg:gap-20 h-full text-white font-bold break-keep'>
2626
<h2 className='text-2xl md:text-[54px] md:leading-[64px] lg:text-[68px] lg:leading-[78px]'>
2727
오로라와 함께하는<br />
2828
여름의 북극 감성 체험

src/app/(with-header)/components/BasePage.tsx

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use client';
22

33
import { useSearchParams } from 'next/navigation';
4+
import { motion } from 'framer-motion';
5+
46
import BannerSection from '@/app/(with-header)/components/BannerSection';
57
import PopularExperiences from '@/app/(with-header)/components/PopularExperiences';
68
import ExperienceList from '@/app/(with-header)/components/ExperienceList';
@@ -12,13 +14,39 @@ export default function BasePage() {
1214

1315
return (
1416
<main>
15-
<BannerSection keyword={keyword} />
17+
<motion.div
18+
initial={{ opacity: 0, y: 20 }}
19+
animate={{ opacity: 1, y: 0 }}
20+
transition={{ duration: 0.6, ease: 'easeOut' }}
21+
>
22+
<BannerSection keyword={keyword} />
23+
</motion.div>
24+
1625
{isSearchMode ? (
17-
<ExperienceList keyword={keyword} isSearchMode />
26+
<motion.div
27+
initial={{ opacity: 0, y: 20 }}
28+
animate={{ opacity: 1, y: 0 }}
29+
transition={{ duration: 0.6, delay: 0.2 }}
30+
>
31+
<ExperienceList keyword={keyword} isSearchMode />
32+
</motion.div>
1833
) : (
1934
<>
20-
<PopularExperiences />
21-
<ExperienceList />
35+
<motion.div
36+
initial={{ opacity: 0, y: 20 }}
37+
animate={{ opacity: 1, y: 0 }}
38+
transition={{ duration: 0.6, delay: 0.2 }}
39+
>
40+
<PopularExperiences />
41+
</motion.div>
42+
43+
<motion.div
44+
initial={{ opacity: 0, y: 20 }}
45+
animate={{ opacity: 1, y: 0 }}
46+
transition={{ duration: 0.6, delay: 0.4 }}
47+
>
48+
<ExperienceList />
49+
</motion.div>
2250
</>
2351
)}
2452
</main>

src/app/(with-header)/components/CategoryFilter.tsx

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import Button from '@/components/Button';
44
import { ACTIVITY_CATEGORIES, ActivityCategory } from '@/constants/categories';
55
import cn from '@/lib/cn';
6+
import { useRef, useState, useEffect } from 'react';
67

78
interface CategoryFilterProps {
89
selectedCategory: ActivityCategory;
@@ -15,20 +16,78 @@ export default function CategoryFilter({
1516
onChange,
1617
className,
1718
}: CategoryFilterProps) {
19+
const scrollRef = useRef<HTMLDivElement | null>(null);
20+
const [hasInteracted, setHasInteracted] = useState(false);
21+
const [isDragging, setIsDragging] = useState(false);
22+
const startX = useRef(0);
23+
const scrollLeft = useRef(0);
24+
25+
const handleFirstInteraction = () => {
26+
if (!hasInteracted) setHasInteracted(true);
27+
};
28+
29+
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
30+
setIsDragging(true);
31+
startX.current = e.pageX - (scrollRef.current?.offsetLeft ?? 0);
32+
scrollLeft.current = scrollRef.current?.scrollLeft ?? 0;
33+
handleFirstInteraction();
34+
};
35+
36+
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
37+
if (!isDragging || !scrollRef.current) return;
38+
e.preventDefault();
39+
const x = e.pageX - scrollRef.current.offsetLeft;
40+
const walk = (x - startX.current) * 1;
41+
scrollRef.current.scrollLeft = scrollLeft.current - walk;
42+
handleFirstInteraction();
43+
};
44+
45+
const handleMouseUpOrLeave = () => {
46+
setIsDragging(false);
47+
};
48+
49+
const handleScroll = () => {
50+
handleFirstInteraction();
51+
};
52+
53+
useEffect(() => {
54+
const el = scrollRef.current;
55+
if (!el) return;
56+
el.addEventListener('scroll', handleScroll);
57+
return () => el.removeEventListener('scroll', handleScroll);
58+
}, []);
59+
1860
return (
19-
<div className={cn('relative flex w-full gap-8 overflow-x-auto whitespace-nowrap no-scrollbar', className)}>
20-
{ACTIVITY_CATEGORIES.map((category) => (
21-
<Button
22-
key={category}
23-
className='flex-shrink-0 max-w-80 max-h-41 py-12 text-[16px] rounded-[15px]'
24-
selected={selectedCategory === category}
25-
variant='category'
26-
onClick={() => onChange(category)}
27-
>
28-
{category}
29-
</Button>
30-
))}
31-
<div className='pointer-events-none absolute top-0 right-0 h-full w-100 bg-gradient-to-l from-white to-transparent' />
61+
<div className='relative w-full'>
62+
{/* 스크롤 가능한 영역 */}
63+
<div
64+
ref={scrollRef}
65+
className={cn(
66+
'relative z-20 flex w-full gap-8 pr-15 overflow-x-auto whitespace-nowrap no-scrollbar cursor-grab active:cursor-grabbing select-none',
67+
className,
68+
)}
69+
onMouseDown={handleMouseDown}
70+
onMouseMove={handleMouseMove}
71+
onMouseUp={handleMouseUpOrLeave}
72+
onMouseLeave={handleMouseUpOrLeave}
73+
>
74+
{ACTIVITY_CATEGORIES.map((category) => (
75+
<Button
76+
key={category}
77+
className='flex-shrink-0 max-w-80 max-h-41 py-12 text-[16px] rounded-[15px]'
78+
selected={selectedCategory === category}
79+
variant='category'
80+
onClick={() => onChange(category)}
81+
>
82+
{category}
83+
</Button>
84+
))}
85+
86+
{/* 그라데이션: 처음만 보이고 상호작용하면 사라짐 */}
87+
{!hasInteracted && (
88+
<div className='md:hidden pointer-events-none absolute top-0 right-0 h-full w-[100px] bg-gradient-to-l from-white to-transparent z-10 transition-opacity duration-300' />
89+
)}
90+
</div>
3291
</div>
3392
);
3493
}

src/app/(with-header)/components/ExperienceCard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ export default function ExperienceCard({
1616
price,
1717
}: Props) {
1818
return (
19-
<div className='flex flex-col w-full gap-16'>
19+
<div className='flex flex-col w-full gap-16 transition-all duration-300 hover:-translate-y-2'>
2020
{/* 썸네일 */}
2121
<div className='relative w-full h-168 md:h-221 lg:h-283 rounded-[20px] overflow-hidden'>
2222
<Image
2323
fill
2424
alt={title}
25-
className='object-cover'
25+
className='object-cover transition-transform duration-300 hover:scale-105'
2626
src={imageUrl}
2727
/>
2828
</div>
@@ -32,7 +32,7 @@ export default function ExperienceCard({
3232
<span className='pb-10 text-lg text-black'>
3333
{rating} <span className='text-gray-700 text-lg'>({reviews})</span>
3434
</span>
35-
<p className='pb-15 text-2lg font-semibold text-black line-clamp-2'>
35+
<p className='mb-15 text-2lg font-semibold text-black line-clamp-2-custom'>
3636
{title}
3737
</p>
3838
<p className='text-xl text-black font-bold'>

src/app/(with-header)/components/ExperienceList.tsx

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList
4444
const totalPage = Math.ceil(totalCount / 8);
4545

4646
return (
47-
<section className='max-w-1200 m-auto px-24 lg:px-0 pb-83'>
47+
<section className='max-w-1200 m-auto px-16 md:px-24 lg:px-0 pb-83'>
4848
{/* 🔍 검색 모드일 때 문구 표시 */}
4949
{isSearchMode && keyword && (
5050
<>
51-
<p className="text-left text-lg font-semibold ml-4 md:ml-0 mt-32">
52-
<span className="text-primary font-bold">"{keyword}"</span> (으)로 검색한 결과입니다.
51+
<p className="text-left pt-24 lg:pt-40 text-black text-2xl md:text-3xl">
52+
<span className="text-primary font-bold">{keyword}</span>(으)로 검색한 결과입니다.
5353
</p>
54-
<p className="text-left text-sm font-normal ml-4 md:ml-0 mt-8 mb-16">
54+
<p className="text-left text-lg font-normal mt-8 mb-16">
5555
<span className="font-semibold">{totalCount}</span>개의 결과
5656
</p>
5757
{experiences.length === 0 && !isLoading && (
@@ -61,29 +61,34 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList
6161
)}
6262

6363
{!isSearchMode && (
64-
<div className='flex justify-between items-center mb-40'>
64+
<div className='relative flex justify-between items-center mb-40 pr-120'>
6565
<CategoryFilter
6666
selectedCategory={selectedCategory}
6767
onChange={(category) => {
6868
setSelectedCategory(category);
6969
setCurrentPage(1);
7070
}}
7171
/>
72-
<Dropdown
73-
className='w-200'
74-
placeholder='가격'
75-
options={SORT_OPTIONS}
76-
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
77-
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
78-
const value = SORT_VALUE_MAP[label];
79-
setSortOption(value);
80-
setCurrentPage(1);
81-
}}
82-
/>
72+
{/* <div className=''> */}
73+
<Dropdown
74+
className='absolute right-0 md:w-130'
75+
buttonClassName='flex flex-row items-center justify-between gap-0 border-nomad border rounded-[15px] text-md py-9 px-15'
76+
listboxClassName='px-0 py-0'
77+
optionClassName='pl-10 pr-0 py-9 text-md'
78+
placeholder='가격'
79+
options={SORT_OPTIONS}
80+
value={sortOption && SORT_LABEL_MAP[sortOption as keyof typeof SORT_LABEL_MAP] || ''}
81+
onChange={(label: keyof typeof SORT_VALUE_MAP) => {
82+
const value = SORT_VALUE_MAP[label];
83+
setSortOption(value);
84+
setCurrentPage(1);
85+
}}
86+
/>
87+
{/* </div> */}
8388
</div>
8489
)}
8590

86-
<div className='m-0'>
91+
<div className='m-0 pb-30 md:pb-150 lg:pb-100'>
8792
{!isSearchMode && (
8893
<h2 className='text-xl md:text-3xl font-bold'>🛼 모든 체험</h2>
8994
)}

src/app/(with-header)/components/PopularCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function PopularCard({
3131
{/* 별점 정보 */}
3232
<span className='text-md'>{rating} ({reviews})</span>
3333
{/* 체험명 (줄바꿈 포함, 반응형 크기) */}
34-
<p className='text-2lg md:text-3xl font-semibold'>{title}</p>
34+
<p className='text-2lg md:text-3xl font-semibold line-clamp-2-custom'>{title}</p>
3535
{/* 가격 정보 */}
3636
<p className='text-lg md:text-xl'>{price.toLocaleString()} <span className='text-gray-600 text-md'>/ 인</span></p>
3737
</div>

0 commit comments

Comments
 (0)