Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/assets/img/default-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 53 additions & 15 deletions src/app/(with-header)/activities/[id]/components/ImageGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,41 @@ import Image from 'next/image';
import React, { useState } from 'react';
import { ImageGridProps } from '@/types/activityDetailType';
import { AnimatePresence, motion } from 'framer-motion';
import Modal from '@/components/Modal';
import { DEFAULT_BG } from '@/constants/AvatarConstants';

function ImageGrid({ mainImage, subImages }: ImageGridProps) {
const images = [mainImage, ...subImages];

const [image, setImage] = useState([mainImage, ...subImages]);
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);

Comment on lines +11 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

props 변경 시 이미지 상태 동기화 필요

초기 렌더에서만 [mainImage, ...subImages]로 state를 설정하므로, props가 갱신되어도 UI가 반영되지 않습니다. 상세 페이지에서 데이터가 비동기로 로드/변경될 수 있다면 동기화 로직을 추가하세요.

아래 수정으로 props 변화에 동기화할 수 있습니다.

-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
 ...
-  const [image, setImage] = useState([mainImage, ...subImages]);
+  const [image, setImage] = useState([mainImage, ...subImages]);
   const [currentIndex, setCurrentIndex] = useState(0);
   const [direction, setDirection] = useState(0);
   const [isOpen, setIsOpen] = useState(false);
   const [selectedImage, setSelectedImage] = useState<string | null>(null);
 
+  useEffect(() => {
+    setImage([mainImage, ...subImages]);
+  }, [mainImage, subImages]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [image, setImage] = useState([mainImage, ...subImages]);
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);
import React, { useEffect, useState } from 'react';
...
const [image, setImage] = useState([mainImage, ...subImages]);
const [currentIndex, setCurrentIndex] = useState(0);
const [direction, setDirection] = useState(0);
const [isOpen, setIsOpen] = useState(false);
const [selectedImage, setSelectedImage] = useState<string | null>(null);
useEffect(() => {
setImage([mainImage, ...subImages]);
}, [mainImage, subImages]);
...
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ImageGrid.tsx around lines
11 to 16, the image state is initialized only on first render with [mainImage,
...subImages] so updates to mainImage/subImages props are not reflected; add a
useEffect that watches mainImage and subImages and calls setImage([mainImage,
...subImages]) (optionally reset currentIndex and selectedImage as needed) to
synchronize state when props change.

const handleImageClick = (image: string) => {
setSelectedImage(image);
setIsOpen(true);
};

const handleImageError = (index: number) => {
setImage((prev) => prev.map((src, i) => (i === index ? DEFAULT_BG : src)));
};

const prevSlide = () => {
setDirection(-1);
setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
setCurrentIndex((prev) => (prev === 0 ? image.length - 1 : prev - 1));
};

const nextSlide = () => {
setDirection(1);
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
setCurrentIndex((prev) => (prev === image.length - 1 ? 0 : prev + 1));
};

const variants = {
enter: (direction: number) => ({
x: direction > 0 ? 300 : -300,
opacity: 0,
}),
center: {
x: 0,
opacity: 1,
},
center: { x: 0, opacity: 1 },
exit: (direction: number) => ({
x: direction > 0 ? -300 : 300,
opacity: 0,
Expand All @@ -55,11 +64,13 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) {
className='absolute inset-0'
>
<Image
src={images[currentIndex]}
alt={` ${currentIndex + 1}`}
src={image[currentIndex]}
alt={`${currentIndex + 1}`}
fill
className='rounded-lg object-cover'
priority
unoptimized
onError={() => handleImageError(currentIndex)}
/>
</motion.div>
</AnimatePresence>
Expand All @@ -81,7 +92,7 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) {
</button>

<div className='absolute bottom-2 left-1/2 flex -translate-x-1/2 gap-1'>
{images.map((_, i) => (
{image.map((_, i) => (
<div
key={i}
className={`h-10 w-10 rounded-full ${
Expand All @@ -94,28 +105,55 @@ function ImageGrid({ mainImage, subImages }: ImageGridProps) {

{/* PC/태블릿 */}
<div className='hidden h-[500px] grid-cols-4 grid-rows-4 gap-6 md:grid'>
<div className='relative col-span-2 row-span-4 hover:animate-pulse'>
<div
onClick={() => handleImageClick(mainImage)}
className='relative col-span-2 row-span-4 hover:animate-pulse'
>
<Image
src={mainImage}
src={image[0]}
alt='메인이미지'
fill
className='rounded-lg object-cover'
onError={() => handleImageError(0)}
/>
</div>
Comment on lines +108 to 119
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

a11y: 클릭 가능한 정적 요소(div) 교체 및 클릭 소스 일관성 수정

  • 정적 요소에 onClick을 부여해 접근성 린트 에러가 발생합니다. 버튼 요소로 교체해 키보드 접근성을 확보하세요.
  • 메인 타일 클릭 시 mainImage(원본)을 넘기고, 표시 소스는 image[0](오류 시 대체)라 불일치합니다. 클릭에도 image[0]을 사용해 일관성을 맞추세요.
  • map 콜백 파라미터명이 state 변수 image와 동일해 가독성이 떨어집니다. src 등으로 변경하세요.

아래 diff를 적용하면 접근성 경고를 해결하고, 클릭 동작의 일관성을 맞출 수 있습니다.

-        <div
-          onClick={() => handleImageClick(mainImage)}
-          className='relative col-span-2 row-span-4 hover:animate-pulse'
-        >
+        <button
+          type='button'
+          aria-label='메인 이미지 확대 보기'
+          onClick={() => handleImageClick(image[0])}
+          className='relative col-span-2 row-span-4 hover:animate-pulse focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
+        >
           <Image
             src={image[0]}
             alt='메인이미지'
             fill
             className='rounded-lg object-cover'
+            sizes="(min-width: 768px) 50vw, 100vw"
             onError={() => handleImageError(0)}
           />
-        </div>
+        </button>
-        {image.slice(1, 5).map((image, index) => (
-          <div
-            key={index + 1}
-            onClick={() => handleImageClick(image)}
-            className='relative col-span-1 row-span-2 h-full hover:animate-pulse'
-          >
-            <Image
-              src={image}
-              alt={`서브이미지 ${index + 1}`}
-              fill
-              className='rounded-lg object-cover'
-              onError={() => handleImageError(index + 1)}
-            />
-          </div>
-        ))}
+        {image.slice(1, 5).map((src, index) => (
+          <button
+            type='button'
+            key={index + 1}
+            aria-label={`서브 이미지 ${index + 1} 확대 보기`}
+            onClick={() => handleImageClick(src)}
+            className='relative col-span-1 row-span-2 h-full hover:animate-pulse focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
+          >
+            <Image
+              src={src}
+              alt={`서브이미지 ${index + 1}`}
+              fill
+              className='rounded-lg object-cover'
+              sizes="(min-width: 768px) 25vw, 100vw"
+              onError={() => handleImageError(index + 1)}
+            />
+          </button>
+        ))}

Also applies to: 120-134

🧰 Tools
🪛 Biome (2.1.2)

[error] 109-113: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 109-113: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ImageGrid.tsx around lines
108-119 (also apply same change to 120-134): replace the clickable static <div>
with a <button> to satisfy accessibility/keyboard interaction, update the click
handler to pass the displayed source (use image[0] / the mapped src) instead of
mainImage so the click payload matches the shown image, and rename the map
callback parameter from image to a neutral name like src to avoid shadowing the
state variable and improve readability; ensure the button keeps the same
classes/visuals and still calls handleImageError with the correct index on image
load error.

{subImages.slice(0, 4).map((image, index) => (
{image.slice(1, 5).map((image, index) => (
<div
key={index}
key={index + 1}
onClick={() => handleImageClick(image)}
className='relative col-span-1 row-span-2 h-full hover:animate-pulse'
>
<Image
src={image}
alt={`서브이미지 ${index + 1}`}
fill
className='rounded-lg object-cover'
onError={() => handleImageError(index + 1)}
/>
</div>
))}
</div>
<Modal onOpenChange={setIsOpen} isOpen={isOpen}>
<Modal.Content className='rounded-md'>
<Modal.Header>
<Modal.Close />
</Modal.Header>
<Modal.Item className='flex items-center justify-center'>
<div className='relative aspect-square w-[1200px]'>
{selectedImage && (
<Image
src={selectedImage}
alt='확대 이미지'
fill
className='rounded-lg object-cover p-18'
/>
)}
</div>
</Modal.Item>

<Modal.Footer></Modal.Footer>
</Modal.Content>
</Modal>
</>
);
}
Expand Down
19 changes: 2 additions & 17 deletions src/app/(with-header)/activities/[id]/components/ReviewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,9 @@ import { privateInstance } from '@/apis/privateInstance';
import ReviewTitle from './ReviewTitle';
import useUserStore from '@/stores/authStore';
import cn from '@/lib/cn';

import ReviewCardSkeleton from './Skeletons/ReviewCardSkeleton';

interface ReviewSectionProps {
activityId: string;
reviewCount: number;
rating: number;
}

interface ReviewProps {
id: string;
user: {
nickname: string;
profileImageUrl: string;
};
createdAt: string;
content: string;
}
import { ReviewSectionProps } from '@/types/activityDetailType';
import { ReviewProps } from '@/types/activityDetailType';

function ReviewSection({
activityId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

import Star from '@assets/svg/star';
import { useState, useEffect } from 'react';
import { ReviewTitleProps } from '@/types/activityDetailType';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

타입 전용 임포트 제안

타입만 사용하므로 import type으로 가져오면 불필요한 번들 영향이 줄어듭니다.

-import { ReviewTitleProps } from '@/types/activityDetailType';
+import type { ReviewTitleProps } from '@/types/activityDetailType';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ReviewTitleProps } from '@/types/activityDetailType';
import type { ReviewTitleProps } from '@/types/activityDetailType';
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/ReviewTitle.tsx around line
5, the import of ReviewTitleProps is used only as a type and should be a
type-only import to avoid affecting the runtime bundle; change the import to use
the TypeScript "import type" form for ReviewTitleProps so it is erased at
compile time and does not impact bundle size.


interface ReviewTitleProps {
reviewCount: number;
rating: number;
}
export default function ReviewTitle({
reviewCount = 0,
rating = 0,
Expand Down
13 changes: 3 additions & 10 deletions src/app/(with-header)/activities/[id]/components/Title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,8 @@ import { useParams, useRouter } from 'next/navigation';
import { useQueryClient } from '@tanstack/react-query';
import { useDeleteActivity } from '../hooks/useDeleteActivity';
import Popup from '@/components/Popup';

interface TitleProps {
title: string;
category: string;
rating: number;
reviewCount: number;
address: string;
isOwner: boolean;
}
import { toast } from 'sonner';
import { TitleProps } from '@/types/activityDetailType';

function Title({
title,
Expand All @@ -45,8 +38,8 @@ function Title({
if (!id) return;
mutate(id as string);
setIsPopupOpen(false);
toast.success('체험이 삭제되었습니다!');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

삭제 성공 Toast가 성공/실패 구분 없이 노출됩니다 — mutate onSuccess/onError로 이동 권장

현재는 API 결과와 무관하게 성공 토스트가 뜹니다. 성공 시에만 success, 실패 시에는 error 토스트를 띄우도록 변경해 주세요.

아래와 같이 수정하면 안전합니다:

   const handleDeleteConfirm = () => {
-    if (!id) return;
-    mutate(id as string);
-    setIsPopupOpen(false);
-    toast.success('체험이 삭제되었습니다!');
+    if (!id) return;
+    const idStr = Array.isArray(id) ? id[0] : (id as string);
+    mutate(idStr, {
+      onSuccess: () => {
+        setIsPopupOpen(false);
+        toast.success('체험이 삭제되었습니다!');
+      },
+      onError: () => {
+        toast.error('삭제에 실패했습니다. 다시 시도해주세요.');
+      },
+    });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
toast.success('체험이 삭제되었습니다!');
const handleDeleteConfirm = () => {
if (!id) return;
const idStr = Array.isArray(id) ? id[0] : (id as string);
mutate(idStr, {
onSuccess: () => {
setIsPopupOpen(false);
toast.success('체험이 삭제되었습니다!');
},
onError: () => {
toast.error('삭제에 실패했습니다. 다시 시도해주세요.');
},
});
};
🤖 Prompt for AI Agents
In src/app/(with-header)/activities/[id]/components/Title.tsx around line 41,
the code currently shows toast.success('체험이 삭제되었습니다!') unconditionally; change
this so the toast is only shown in the mutation callbacks: remove the
unconditional toast call and instead pass onSuccess and onError handlers to the
delete mutation (or to mutateAsync's .then/.catch) that call toast.success(...)
on success and toast.error(...) on failure, and ensure any state updates or
navigation happen inside onSuccess so they run only after a confirmed successful
response.

};


return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const useDeleteActivity = () => {
});
queryClient.invalidateQueries({ queryKey: ['popularExperiences'] });
router.push(`/`);
toast.success('체험을 삭제했습니다!');
},
onError: (error: AxiosError) => {
const responseData = error.response?.data as
Expand Down
58 changes: 0 additions & 58 deletions src/app/(with-header)/activities/[id]/mock/mock.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import { ScheduleSelectForm } from '../../components/ScheduleSelectForm';
import { ImageSection } from '../../components/ImageSection';
import { useEditActivityForm } from '../hooks/useEditActivityForm';
import EditActivityFormSkeleton from '../../loading';

interface SubImageType {
id?: number;
url: string | File;
}
import { SubImageType } from '@/types/addEditExperienceType';

export default function EditActivityForm() {
const {
Expand Down
20 changes: 11 additions & 9 deletions src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,7 @@ import { ActivityDetailEdit, Schedule } from '@/types/activityDetailType';
import { AxiosError } from 'axios';
import { toast } from 'sonner';
import { notFound } from 'next/navigation';
import { validateSchedules } from '../../utils/dateValidatoin';

interface SubImageType {
id?: number;
url: string | File;
}
import { SubImageType } from '@/types/addEditExperienceType';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

타입 전용 임포트 제안

타입은 import type으로 가져오는 것이 모범사례입니다.

-import { SubImageType } from '@/types/addEditExperienceType';
+import type { SubImageType } from '@/types/addEditExperienceType';
🤖 Prompt for AI Agents
In src/app/(with-header)/myactivity/[id]/hooks/useEditActivityForm.ts around
line 12, the SubImageType is imported as a regular value import; change it to a
type-only import by replacing the statement with "import type { SubImageType }
from '@/types/addEditExperienceType';" so the compiler treats it as a type-only
import and it is erased from emitted JS (avoids accidental runtime dependency).


export const useEditActivityForm = () => {
const { id } = useParams() as { id: string };
Expand Down Expand Up @@ -209,9 +204,16 @@ export const useEditActivityForm = () => {

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const validationMessage = validateSchedules(dates);
if (validationMessage) {
toast.error(validationMessage);
if (
!title ||
!category ||
!description ||
!address ||
!price ||
!mainImage ||
dates.length === 0
) {
toast.error('소개이미지를 제외한 모든값은 필수값입니다!');
return;
Comment on lines +207 to 217
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

검증 메시지와 로직 불일치 (메인 이미지 필수인데 “제외”) + 스케줄 필드 공란 검증 권장

메인 이미지를 필수로 요구하면서 메시지는 반대로 안내합니다. 또한 스케줄 항목의 날짜/시간 공란도 빠르게 검증하면 UX가 좋아집니다.

   if (
     !title ||
     !category ||
     !description ||
     !address ||
     !price ||
     !mainImage ||
-    dates.length === 0
+    dates.length === 0 ||
+    dates.some((d) => !d.date || !d.startTime || !d.endTime)
   ) {
-    toast.error('소개이미지를 제외한 모든값은 필수값입니다!');
+    toast.error('모든 값은 필수값입니다!');
     return;
   }

추가로, 생성 훅(useCreateActivityForm)과 본 훅의 검증 메시지를 통일하면 사용자 혼란을 줄일 수 있습니다.

원하시면 생성/수정 공통 폼 검증 유틸(예: validateActivityForm)을 만들어 두 훅에서 공유하도록 PR 보조 커밋을 제안드릴 수 있습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
!title ||
!category ||
!description ||
!address ||
!price ||
!mainImage ||
dates.length === 0
) {
toast.error('소개이미지를 제외한 모든값은 필수값입니다!');
return;
if (
!title ||
!category ||
!description ||
!address ||
!price ||
!mainImage ||
dates.length === 0 ||
dates.some((d) => !d.date || !d.startTime || !d.endTime)
) {
toast.error('모든 값은 필수값입니다!');
return;
}

}
try {
Expand Down
16 changes: 2 additions & 14 deletions src/app/(with-header)/myactivity/components/AddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,8 @@ import DaumPostcode from 'react-daum-postcode';
import { useState } from 'react';
import Input from '@/components/Input';
import Button from '@/components/Button';

interface AddressInputProps {
onAddressChange: (address: string) => void;
address: string;
}

interface PostcodeData {
address: string;
addressType: 'R' | 'J';
bname: string;
buildingName: string;
zonecode: string;
userSelectedType: string;
}
import { AddressInputProps } from '@/types/addEditExperienceType';
import { PostcodeData } from '@/types/addEditExperienceType';

export default function AddressInput({
onAddressChange,
Expand Down
34 changes: 21 additions & 13 deletions src/app/(with-header)/myactivity/components/CategoryInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import ChevronIcon from '@assets/svg/chevron'; // 아이콘 경로는 맞게 조정

interface CategoryProps {
category?: string;
onCategoryChange: (value: string) => void;
}
import { CategoryProps } from '@/types/addEditExperienceType';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

타입 전용 임포트로 미묘한 번들/트리셰이킹 이슈 예방

타입만 사용하는 경우 import type을 사용하는 것이 TS/빌드 파이프라인에 안전합니다.

-import { CategoryProps } from '@/types/addEditExperienceType';
+import type { CategoryProps } from '@/types/addEditExperienceType';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { CategoryProps } from '@/types/addEditExperienceType';
import type { CategoryProps } from '@/types/addEditExperienceType';
🤖 Prompt for AI Agents
In src/app/(with-header)/myactivity/components/CategoryInput.tsx around line 2,
the import for CategoryProps is used only as a type; change the statement to a
type-only import (use "import type { CategoryProps } from
'@/types/addEditExperienceType';") to avoid potential bundle/tree-shaking issues
and keep TS build behavior correct.


export default function CategoryInput({
category,
Expand All @@ -16,7 +12,7 @@ export default function CategoryInput({
</label> */}
<div className='relative w-full'>
<select
className={`appearance-none w-full rounded-md border border-gray-800 bg-white px-20 py-17 ${
className={`w-full appearance-none rounded-md border border-gray-800 bg-white px-20 py-17 ${
category ? 'text-black' : 'text-gray-400'
}`}
id='category'
Expand All @@ -26,16 +22,28 @@ export default function CategoryInput({
<option value='' disabled hidden>
카테고리
</option>
<option className='text-black' value='문화 · 예술'>문화 · 예술</option>
<option className='text-black' value='식음료'>식음료</option>
<option className='text-black' value='스포츠'>스포츠</option>
<option className='text-black' value='투어'>투어</option>
<option className='text-black' value='관광'>관광</option>
<option className='text-black' value='웰빙'>웰빙</option>
<option className='text-black' value='문화 · 예술'>
문화 · 예술
</option>
<option className='text-black' value='식음료'>
식음료
</option>
<option className='text-black' value='스포츠'>
스포츠
</option>
<option className='text-black' value='투어'>
투어
</option>
<option className='text-black' value='관광'>
관광
</option>
<option className='text-black' value='웰빙'>
웰빙
</option>
Comment on lines +25 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

하드코딩된 옵션을 상수/타입으로 관리해 중복 제거

동일 카테고리 목록이 여러 컴포넌트에 흩어지면 유지보수 비용이 커집니다. 상수(또는 전역 ACTIVITY_CATEGORIES)를 순회 렌더링으로 바꾸는 것을 권장합니다.

-          <option className='text-black' value='문화 · 예술'>
-            문화 · 예술
-          </option>
-          <option className='text-black' value='식음료'>
-            식음료
-          </option>
-          <option className='text-black' value='스포츠'>
-            스포츠
-          </option>
-          <option className='text-black' value='투어'>
-            투어
-          </option>
-          <option className='text-black' value='관광'>
-            관광
-          </option>
-          <option className='text-black' value='웰빙'>
-            웰빙
-          </option>
+          {CATEGORIES.map((c) => (
+            <option key={c} className='text-black' value={c}>
+              {c}
+            </option>
+          ))}

추가(파일 상단 등) 코드 예시:

// 가능하면 공용 상수(예: ACTIVITY_CATEGORIES)를 import 해 사용하세요.
// 임시로 로컬 상수 사용 시:
const CATEGORIES = ['문화 · 예술', '식음료', '스포츠', '투어', '관광', '웰빙'] as const;
🤖 Prompt for AI Agents
In src/app/(with-header)/myactivity/components/CategoryInput.tsx around lines 25
to 42, the option elements are hardcoded which duplicates category data; replace
them by importing or declaring a single constant array (e.g.,
ACTIVITY_CATEGORIES or local CATEGORIES typed as readonly string[] / 'as const')
and map over that array to render <option> elements, preserving value, display
text and className; export the constant from a shared module if used elsewhere
to avoid duplication and update types (union or string) as needed.

</select>

{/* 커스텀 화살표 아이콘 */}
<div className='pointer-events-none absolute right-12 top-1/2 -translate-y-1/2'>
<div className='pointer-events-none absolute top-1/2 right-12 -translate-y-1/2'>
<ChevronIcon size={20} />
</div>
</div>
Expand Down
Loading