-
Notifications
You must be signed in to change notification settings - Fork 1
Fix/113 헤더, 푸터, 메인 페이지 이슈 해결 #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ce85423
d57d07d
7d93083
ea9a8ce
e04b647
8a98bd6
c6df176
345904c
1a52803
25a17b0
f641aac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const IconGithub = ({ size = 24, color = '#fff', ...props }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <svg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width={size} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height={size} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| viewBox="0 0 98 96" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fill={color} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| xmlns="http://www.w3.org/2000/svg" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fillRule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clipRule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a47 47 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </svg> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 접근성 개선을 위해 title 요소 또는 aria-label 추가가 필요합니다. 정적 분석 도구에서 지적한 대로, 접근성을 위해 SVG에 대한 대체 텍스트가 필요합니다. 다음 중 하나의 방법으로 해결할 수 있습니다: <svg
width={size}
height={size}
viewBox="0 0 98 96"
fill={color}
xmlns="http://www.w3.org/2000/svg"
+ aria-label="GitHub"
{...props}
>
+ <title>GitHub</title>
<path📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 4-11: Alternative text title element cannot be empty For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute. (lint/a11y/noSvgWithoutTitle) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default IconGithub; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| 'use client'; | ||
|
|
||
| import { useSearchParams } from 'next/navigation'; | ||
| import BannerSection from '@/app/(with-header)/components/BannerSection'; | ||
| import PopularExperiences from '@/app/(with-header)/components/PopularExperiences'; | ||
| import ExperienceList from '@/app/(with-header)/components/ExperienceList'; | ||
|
|
||
| export default function BasePage() { | ||
| const searchParams = useSearchParams(); | ||
| const keyword = searchParams.get('q') ?? ''; | ||
| const isSearchMode = Boolean(keyword.trim()); | ||
|
|
||
| return ( | ||
| <main> | ||
| <BannerSection keyword={keyword} /> | ||
| {isSearchMode ? ( | ||
| <ExperienceList keyword={keyword} isSearchMode /> | ||
| ) : ( | ||
| <> | ||
| <PopularExperiences /> | ||
| <ExperienceList /> | ||
| </> | ||
| )} | ||
| </main> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import Dropdown from '@components/Dropdown'; | |
| import Pagination from '@components/Pagination'; | ||
| import CategoryFilter from '@/app/(with-header)/components/CategoryFilter'; | ||
| import ExperienceCard from '@/app/(with-header)/components/ExperienceCard'; | ||
| import ExperienceCardSkeleton from '@/app/(with-header)/components/Skeletons/ExperienceCardSkeleton'; | ||
| import { getExperiences, ExperienceListResult } from '@/app/api/experiences/getExperiences'; | ||
| import { ACTIVITY_CATEGORIES, ActivityCategory } from '@/constants/categories'; | ||
| import { | ||
|
|
@@ -26,15 +27,14 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList | |
| const [selectedCategory, setSelectedCategory] = useState<ActivityCategory>(ACTIVITY_CATEGORIES[0]); | ||
| const [sortOption, setSortOption] = useState<string | undefined>(''); | ||
|
|
||
| // TanStack Query 사용 (타입 명시 필수) | ||
| const { data, isLoading, error } = useQuery<ExperienceListResult>({ | ||
| const { data, isLoading } = useQuery<ExperienceListResult>({ | ||
| queryKey: ['experiences', currentPage, selectedCategory, sortOption, keyword], | ||
| queryFn: () => | ||
| getExperiences({ | ||
| page: currentPage, | ||
| sort: sortOption, | ||
| category: selectedCategory, | ||
| keyword, | ||
| keyword: keyword || undefined, | ||
| }), | ||
| placeholderData: (prev) => prev, | ||
| }); | ||
|
Comment on lines
+30
to
40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain에러 처리 고려사항
다음 스크립트로 에러 처리 패턴을 확인해보겠습니다: 🏁 Script executed: #!/bin/bash
# Description: Check error handling patterns in similar components
# Search for error handling in other list components
rg -A 10 -B 5 "useQuery.*error" --type tsxLength of output: 72 다음 스크립트로 TSX 파일들에서 🏁 Script executed: #!/bin/bash
# Description: TSX 컴포넌트에서 useQuery 호출 및 error 구조분해 패턴 검색
# 1. useQuery 사용 파일 목록
echo "■ useQuery 사용 TSX 파일:"
rg -l "useQuery(" -g '*.tsx' || true
echo ""
# 2. useQuery 호출 내 error 구조분해 또는 error 사용 패턴
echo "■ useQuery 호출에서 error 구조분해/사용 패턴:"
rg -n -C2 "{[^}]*error" -g '*.tsx' || trueLength of output: 378 TSX 파일에서 🏁 Script executed: #!/bin/bash
# TSX 컴포넌트에서 useQuery 호출 및 error 구조분해/사용 패턴 검색
echo "■ useQuery 사용 파일:"
rg -l "useQuery" -g '*.tsx' || true
echo ""
echo "■ useQuery 호출 라인:"
rg -n "useQuery" -g '*.tsx' || true
echo ""
echo "■ error 구조분해 사용 패턴 (예: const { data, error } = useQuery(...)):"
rg -n "const \\{[^\\}]*error[^\\}]*\\}" -g '*.tsx' || trueLength of output: 2270 ExperienceList.tsx에 에러 핸들링 추가 필요 다른 목록 컴포넌트(e.g. PopularExperiences.tsx)에서는 • 파일:
🤖 Prompt for AI Agents |
||
|
|
@@ -44,7 +44,8 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList | |
| const totalPage = Math.ceil(totalCount / 8); | ||
|
|
||
| return ( | ||
| <section className='max-w-1200 m-auto px-24 md:px-0 pb-83'> | ||
| <section className='max-w-1200 m-auto px-24 lg:px-0 pb-83'> | ||
| {/* 🔍 검색 모드일 때 문구 표시 */} | ||
| {isSearchMode && keyword && ( | ||
| <> | ||
| <p className="text-left text-lg font-semibold ml-4 md:ml-0 mt-32"> | ||
|
|
@@ -53,7 +54,7 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList | |
| <p className="text-left text-sm font-normal ml-4 md:ml-0 mt-8 mb-16"> | ||
| 총 <span className="font-semibold">{totalCount}</span>개의 결과 | ||
| </p> | ||
| {experiences.length === 0 && ( | ||
| {experiences.length === 0 && !isLoading && ( | ||
| <p className="text-center text-gray-500 mt-32">검색 결과가 없습니다.</p> | ||
| )} | ||
| </> | ||
|
|
@@ -87,13 +88,12 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList | |
| <h2 className='text-xl md:text-3xl font-bold'>🛼 모든 체험</h2> | ||
| )} | ||
|
|
||
| {isLoading ? ( | ||
| <p className="text-center">체험을 불러오는 중입니다...</p> | ||
| ) : error ? ( | ||
| <p className="text-center text-red-500">체험 데이터를 불러오는 데 실패했습니다 😢</p> | ||
| ) : ( | ||
| <div className='grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8 md:gap-16 lg:gap-24 mt-24'> | ||
| {experiences.map((exp) => ( | ||
| {/* 체험 카드 목록 */} | ||
| <div className='grid grid-cols-2 grid-rows-2 md:grid-cols-3 md:grid-rows-3 lg:grid-cols-4 lg:grid-rows-2 gap-8 md:gap-16 lg:gap-24 mt-24'> | ||
| {isLoading ? ( | ||
| Array.from({ length: 8 }).map((_, idx) => <ExperienceCardSkeleton key={idx} />) | ||
| ) : ( | ||
| experiences.map((exp) => ( | ||
| <Link key={exp.id} href={`/activities/${exp.id}`}> | ||
| <ExperienceCard | ||
| imageUrl={exp.bannerImageUrl} | ||
|
|
@@ -103,9 +103,9 @@ export default function ExperienceList({ keyword, isSearchMode }: ExperienceList | |
| title={exp.title} | ||
| /> | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| )} | ||
| )) | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
||
| {experiences.length > 0 && ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,25 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import IconArrowLeft from '@assets/svg/left-arrow'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import IconArrowRight from '@assets/svg/right-arrow'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useRef } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Link from 'next/link'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useQuery } from '@tanstack/react-query'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import IconArrowLeft from '@assets/svg/left-arrow'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import IconArrowRight from '@assets/svg/right-arrow'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import PopularCard from '@/app/(with-header)/components/PopularCard'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import PopularCardSkeleton from '@/app/(with-header)/components/Skeletons/PopularCardSkeleton'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getPopularExperiences } from '@/app/api/experiences/getPopularExperiences'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function PopularExperiences() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sliderRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TanStack Query 도입 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data, isLoading, error } = useQuery({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryKey: ['popularExperiences'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queryFn: getPopularExperiences, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const scrollByCard = (direction: 'left' | 'right') => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!sliderRef.current) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const card = sliderRef.current.querySelector('.card'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(card instanceof HTMLElement)) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -33,11 +36,9 @@ export default function PopularExperiences() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isLoading) return <p className="text-center">인기 체험을 불러오는 중입니다...</p>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (error || !data) return <p className="text-center text-red-500">데이터를 불러오는 데 실패했어요 😢</p>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <section className='pt-24 md:pt-34 pl-24 lg:pl-0 pb-40 lg:pb-33 lg:max-w-1200 lg:w-full mx-auto'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 제목 + 버튼 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex justify-between items-center pb-16 md:pb-32 mb-6'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2 className='text-xl md:text-3xl font-bold'>🔥 인기 체험</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className='flex gap-2'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -46,23 +47,37 @@ export default function PopularExperiences() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* 카드 영역 */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={sliderRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className='flex gap-16 md:gap-32 lg:gap-24 overflow-x-auto scroll-smooth no-scrollbar' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {data.activities.map((exp) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={exp.id} className='flex-shrink-0 card'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Link href={`/activities/${exp.id}`} className='flex-shrink-0 card'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PopularCard | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl={exp.bannerImageUrl} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| price={exp.price} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rating={exp.rating} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reviews={exp.reviewCount} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={exp.title} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {error ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-red-500 text-sm">인기 체험을 불러오는 데 실패했습니다 😢</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : isLoading || !data ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Array.from({ length: 4 }).map((_, idx) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={idx} className="flex-shrink-0 card"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PopularCardSkeleton /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.activities.map((exp) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={exp.id} className='flex-shrink-0 card'> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Link | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={`/activities/${exp.id}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className='flex-shrink-0 card' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PopularCard | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imageUrl={exp.bannerImageUrl} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| price={exp.price} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rating={exp.rating} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reviews={exp.reviewCount} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title={exp.title} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 로딩 상태 처리 개선이 우수합니다. early return 패턴에서 조건부 렌더링으로 변경한 것이 좋습니다. 다음과 같은 장점이 있습니다:
다만 한 가지 개선사항이 있습니다: 65-68번 라인에서 - <div key={exp.id} className='flex-shrink-0 card'>
- <Link
- href={`/activities/${exp.id}`}
- className='flex-shrink-0 card'
- >
+ <div key={exp.id} className='flex-shrink-0 card'>
+ <Link href={`/activities/${exp.id}`}>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export default function ExperienceCardSkeleton() { | ||
| return ( | ||
| <div className="flex flex-col w-full gap-16 animate-pulse"> | ||
| {/* 썸네일 영역 */} | ||
| <div className="w-full h-168 md:h-221 lg:h-283 rounded-[20px] bg-gray-200" /> | ||
|
|
||
| {/* 텍스트 영역 */} | ||
| <div className="flex flex-col gap-10"> | ||
| <div className="w-1/2 h-16 bg-gray-200 rounded" /> | ||
| <div className="w-full h-20 bg-gray-200 rounded" /> | ||
| <div className="w-2/3 h-16 bg-gray-200 rounded" /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export default function PopularCardSkeleton() { | ||
| return ( | ||
| <div className="relative w-186 h-186 md:w-384 md:h-384 rounded-[20px] overflow-hidden shadow-md bg-gray-200 animate-pulse"> | ||
| {/* 어두운 오버레이 레이어 */} | ||
| <div className="absolute inset-0 bg-gradient-to-r from-black to-transparent opacity-50" /> | ||
|
|
||
| {/* 텍스트 정보 블록 위치 */} | ||
| <div className="absolute bottom-12 flex flex-col gap-6 md:gap-20 px-20 py-12"> | ||
| <div className="w-24 h-16 rounded bg-gray-300" /> {/* 별점 */} | ||
| <div className="w-40 h-24 md:w-60 md:h-28 rounded bg-gray-300" /> {/* 타이틀 */} | ||
| <div className="w-32 h-16 md:w-40 md:h-20 rounded bg-gray-300" /> {/* 가격 */} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,7 @@ | ||
| 'use client'; | ||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| import { useState } from 'react'; | ||
| import BannerSection from '@/app/(with-header)/components/BannerSection'; | ||
| import PopularExperiences from '@/app/(with-header)/components/PopularExperiences'; | ||
| import ExperienceList from '@/app/(with-header)/components/ExperienceList'; | ||
| import BasePage from '@/app/(with-header)/components/BasePage'; | ||
|
|
||
| export default function HomePage() { | ||
| const [searchKeyword, setSearchKeyword] = useState(''); | ||
|
|
||
| return ( | ||
| <main> | ||
| <BannerSection onSearch={setSearchKeyword} /> | ||
|
|
||
| {searchKeyword ? ( | ||
| <ExperienceList keyword={searchKeyword} isSearchMode /> | ||
| ) : ( | ||
| <> | ||
| <PopularExperiences /> | ||
| <ExperienceList /> | ||
| </> | ||
| )} | ||
| </main> | ||
| ); | ||
| } | ||
| export default function Page() { | ||
| return <BasePage />; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
TypeScript 타입 정의 추가가 필요합니다.
컴포넌트의 props에 대한 TypeScript 인터페이스가 없어 타입 안전성이 보장되지 않습니다.
다음과 같이 props 타입을 정의해주세요:
📝 Committable suggestion
🤖 Prompt for AI Agents