Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
132 changes: 132 additions & 0 deletions improvement-suggestions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# 코드 개선 제안사항

## 성능 (Performance)

### 최적화
- #P101 컴포넌트 지연 로딩(lazy loading) 도입 검토 `MainContent.tsx`
- #P102 이미지 및 컨텐츠 최적화 `MainContent.tsx`
- #P201 React Query 캐시 전략 최적화 `GatheringDetailInformation.tsx`
- #P202 불필요한 리렌더링 방지 `GatheringDetailInformation.tsx`
- #P301 모달 컴포넌트 지연 로딩 적용 `CreateGatheringModalWrapper.tsx`
- #P401 불필요한 리렌더링 최적화를 위한 메모이제이션 검토 `LoginForm.tsx`
- #P402 상태 업데이트 로직 최적화 `LoginForm.tsx`
- #P403 유효성 검사 로직 최적화 `LoginForm.tsx`
- #P404 디바운스 시간 조정 및 성능 모니터링 `LoginForm.tsx`
- #P501 Zustand 스토어 선택자 메모이제이션 `FavoriteList.tsx`
- #P502 필터링 로직 최적화 `FavoriteList.tsx`
- #P503 데이터 페칭 및 캐싱 전략 최적화 `FavoriteList.tsx`
- #P504 클라이언트 사이드 필터링 최적화 `FavoriteList.tsx`
- #P701 이미지 업로드 최적화 `ImageUploader.tsx`
- #P801 네비게이션 상태 관리 최적화 `NavList.tsx`
- #P802 리렌더링 최적화 `NavList.tsx`
- #P803 경로 비교 로직 최적화 `NavList.tsx`
- #P901 프로필 이미지 최적화 `MyProfile.tsx`
- #P902 상태 관리 최적화 `MyProfile.tsx`
- #P1001 이미지 업로드 최적화 `EditProfileModal.tsx`
- #P1002 상태 업데이트 최적화 `EditProfileModal.tsx`

## 접근성 (Accessibility)

### ARIA 및 의미론적 마크업
- #A101 랜드마크 역할(role="main") 추가 `MainContent.tsx`
- #A201 의미론적 HTML 구조 개선 `GatheringDetailInformation.tsx`
- #A202 섹션 제목 및 설명 추가 `GatheringDetailInformation.tsx`
- #A203 진행률 표시에 대한 접근성 개선 `GatheringDetailInformation.tsx`
- #A301 폼 유효성 검사 결과 스크린 리더 지원 `LoginForm.tsx`
- #A302 폼 레이아웃 및 키보드 탐색 개선 `LoginForm.tsx`
- #A401 리뷰 목록 탐색 및 필터링 접근성 개선 `ReviewListParent.tsx`
- #A402 리뷰 목록 랜드마크 및 헤딩 구조 개선 `ReviewListParent.tsx`
- #A501 필터링 및 정렬 컨트롤 접근성 개선 `FavoriteList.tsx`
- #A502 리스트 구조 및 랜드마크 개선 `FavoriteList.tsx`
- #A601 ARIA 역할 및 속성 강화 `AlertModal.tsx`
- #A602 모달 포커스 관리 개선 `AlertModal.tsx`
- #A701 파일 업로드 상태 알림 `ImageUploader.tsx`
- #A801 네비게이션 구조 및 키보드 탐색 개선 `NavList.tsx`
- #A802 모바일 버튼 접근성 개선 `NavList.tsx`
- #A803 네비게이션 아이템 접근성 강화 `NavList.tsx`
- #A804 전체 네비게이션 구조 개선 `NavList.tsx`
- #A901 프로필 섹션 구조 개선 `MyProfile.tsx`
- #A1001 모달 키보드 탐색 개선 `EditProfileModal.tsx`

## 타입 안정성 (Type Safety)

### 타입 정의 및 검증
- #T201 API 응답 데이터 타입 정의 강화 `GatheringDetailInformation.tsx`
- #T202 props 인터페이스 문서화 `GatheringDetailInformation.tsx`
- #T301 Zustand 스토어 타입 안정성 강화 `CreateGatheringModalWrapper.tsx`
- #T401 URL 파라미터 타입 안정성 강화 `ReviewListParent.tsx`
- #T402 검색 파라미터 타입 검증 및 기본값 처리 `ReviewListParent.tsx`
- #T403 쿼리 파라미터 타입 안정성 강화 `ReviewListParent.tsx`
- #T501 로그인 폼 타입 정의 개선 및 문서화 `LoginForm.tsx`
- #T502 react-hook-form 타입 안정성 강화 `LoginForm.tsx`
- #T601 props 인터페이스 문서화 `AlertModal.tsx`
- #T602 콜백 함수 타입 명시적 정의 `AlertModal.tsx`
- #T701 props 인터페이스 문서화 `ImageUploader.tsx`
- #T702 파일 객체 타입 제한 `ImageUploader.tsx`
- #T801 Zustand 스토어 타입 안정성 강화 `NavList.tsx`
- #T802 props 인터페이스 문서화 `NavList.tsx`
- #T803 활성화 상태 타입 강화 `NavList.tsx`
- #T804 props 인터페이스 문서화 `NavList.tsx`
- #T901 사용자 데이터 타입 안정성 강화 `MyProfile.tsx`
- #T1001 프로필 수정 폼 타입 안정성 강화 `EditProfileModal.tsx`
- #T1002 props 인터페이스 문서화 `EditProfileModal.tsx`
- #T1003 콜백 함수 타입 명시적 정의 `EditProfileModal.tsx`

## 사용자 경험 (User Experience)

### UI/UX 개선
- #U101 스켈레톤 UI 적용으로 로딩 체감 개선 `MainContent.tsx`
- #U201 실시간 참여자 수 업데이트 `GatheringDetailInformation.tsx`
- #U202 참여 가능 여부 시각적 피드백 강화 `GatheringDetailInformation.tsx`
- #U301 모달 애니메이션 개선 `CreateGatheringModalWrapper.tsx`
- #U302 모달 외부 클릭 시 확인 프롬프트 추가 `CreateGatheringModalWrapper.tsx`
- #U303 버튼 호버/포커스 상태 개선 `CreateGatheringModalWrapper.tsx`
- #U304 모달 전환 애니메이션 추가 `CreateGatheringModalWrapper.tsx`
- #U401 스크롤 위치 복원 기능 추가 `ReviewListParent.tsx`
- #U402 스크롤 로딩 UX 개선 `ReviewListParent.tsx`
- #U403 로딩 상태 스켈레톤 UI 적용 `ReviewListParent.tsx`
- #U501 필터 변경 시 애니메이션 추가 `FavoriteList.tsx`
- #U502 로딩 및 빈 상태 UX 개선 `FavoriteList.tsx`
- #U601 로그인 상태 유지 옵션 추가 `LoginForm.tsx`
- #U602 실시간 유효성 검사 피드백 제공 `LoginForm.tsx`
- #U603 로딩 상태 UI/UX 개선 `LoginForm.tsx`
- #U701 드래그 앤 드롭 지원 `ImageUploader.tsx`
- #U702 업로드 진행률 표시 `ImageUploader.tsx`
- #U801 반응형 전환 애니메이션 추가 `NavList.tsx`
- #U802 활성화 상태 전환 효과 `NavList.tsx`
- #U803 네비게이션 상태 피드백 `NavList.tsx`
- #U901 사용자 경험: 프로필 수정 상호작용 개선 `MyProfile.tsx`
- #U1001 이미지 업로드 피드백 개선 `EditProfileModal.tsx`

## 에러 처리 (Error Handling)

### 에러 관리 및 복구
- #E301 데이터 로딩 실패 시 폴백 UI 추가 `GatheringDetailInformation.tsx`
- #E401 로그인 상태 변경 시 에러 처리 `CreateGatheringModalWrapper.tsx`
- #E501 데이터 페칭 실패 시 재시도 로직 추가 `ReviewListParent.tsx`
- #E502 데이터 로딩 상태 처리 개선 `ReviewListParent.tsx`
- #E601 데이터 로딩 실패 시 에러 처리 `FavoriteList.tsx`
- #E602 데이터 로딩 상태 처리 개선 `FavoriteList.tsx`
- #E603 필터 상태 초기화 오류 처리 `FavoriteList.tsx`
- #E701 콜백 함수 실행 오류 처리 `AlertModal.tsx`
- #E702 콜백 함수 실행 순서 보장 `AlertModal.tsx`
- #E801 네트워크 오류 및 서버 응답 타임아웃 처리 `LoginForm.tsx`
- #E802 의존성 배열 최적화 및 cleanup 함수 추가 `LoginForm.tsx`
- #E803 서버 응답 에러 상태 처리 개선 `LoginForm.tsx`
- #E901 로그아웃 실패 처리 `NavList.tsx`
- #E902 잘못된 경로 처리 `NavList.tsx`
- #E1001 사용자 데이터 누락 처리 `MyProfile.tsx`
- #E1101 이미지 업로드 실패 처리 `EditProfileModal.tsx`
- #E1102 파일 선택 취소 처리 `EditProfileModal.tsx`
- #E1103 서버 응답 에러 처리 `EditProfileModal.tsx`

## 보안 (Security)

### 보안 강화
- #S701 CSRF 토큰 검증 및 XSS 방지 처리 강화 `LoginForm.tsx`
- #S702 비밀번호 입력 시 자동완성 정책 검토 `LoginForm.tsx`
- #S703 폼 데이터 전송 전 sanitization 처리 `LoginForm.tsx`
- #S801 파일 업로드 검증 강화 `ImageUploader.tsx`
- #S802 파일 MIME 타입 검증 `ImageUploader.tsx`
- #S1001 이미지 파일 검증 `EditProfileModal.tsx`
- #S1002 폼 데이터 검증 `EditProfileModal.tsx`
8 changes: 7 additions & 1 deletion src/app/(home)/_components/MainContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/**
* #P101 성능: 컴포넌트 지연 로딩(lazy loading) 도입 검토
* #P102 성능: 이미지 및 컨텐츠 최적화
* #A101 접근성: 랜드마크 역할(role="main") 추가
* #U101 사용자 경험: 스켈레톤 UI 적용으로 로딩 체감 개선
*/
import MainCarouselContainer from "./MainCarouselContainer";
import MainPromotionContainer from "./MainPromotionContainer";
import MainReviewContent from "./MainReviewContent";

const MainContent = () => {
return (
<div className="base-wrap mb-20 flex flex-col gap-40">
<div className="base-wrap mb-20 flex flex-col gap-40" role="main">
<MainCarouselContainer />
<MainPromotionContainer />
<MainReviewContent />
Expand Down
30 changes: 29 additions & 1 deletion src/app/all-reviews/[tab]/_components/ReviewListParent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"use client";

/**
* #P401 성능: 무한 스크롤 최적화 및 가상화 도입 검토
* #T401 타입: URL 파라미터 타입 안정성 강화
* #E501 에러: 데이터 페칭 실패 시 재시도 로직 추가
*/
import { useParams, useSearchParams } from "next/navigation";

import LoadingSpinner from "@/components/common/LoadingSpinner";
Expand All @@ -9,12 +14,18 @@ import { useSuspenseReviewList } from "@/hooks/review/useReviewList";
import useIntersectionObserver from "@/hooks/useIntersectionObserver";
import { TReviewQueryParams } from "@/types/review";

/**
* #P402 성능: 검색 파라미터 변경 시 불필요한 리렌더링 방지
* #A401 접근성: 리뷰 목록 탐색 및 필터링 접근성 개선
* #U401 사용자 경험: 스크롤 위치 복원 기능 추가
*/
const ReviewListParent = () => {
const { tab } = useParams();
const searchParams = useSearchParams();
const online = SERVICE_LIST.ONLINE;
const isOnline = tab === online.value;

/** #T402 타입: 검색 파라미터 타입 검증 및 기본값 처리 */
const type = isOnline ? online.type : searchParams.get("type");
let location;
if (searchParams.get("location") !== "전체") {
Expand All @@ -26,6 +37,7 @@ const ReviewListParent = () => {
const sortBy = searchParams.get("sortBy");
const sortOrder = searchParams.get("sortOrder");

/** #T403 타입: 쿼리 파라미터 타입 안정성 강화 */
const reviewQueryParams: TReviewQueryParams = {
...(type && { type }),
...(sortBy && { sortBy }),
Expand All @@ -34,11 +46,19 @@ const ReviewListParent = () => {
...(date && { date }),
};

/**
* #P403 성능: 데이터 캐싱 및 프리페칭 전략 수립
* #E502 에러: 데이터 로딩 상태 처리 개선
*/
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
useSuspenseReviewList(!isOnline, reviewQueryParams);

const flatData = data?.pages.flatMap(page => page.data);

/**
* #P404 성능: Intersection Observer 콜백 최적화
* #U402 사용자 경험: 스크롤 로딩 UX 개선
*/
const onIntersect: IntersectionObserverCallback = ([{ isIntersecting }]) => {
if (isIntersecting && hasNextPage) {
fetchNextPage();
Expand All @@ -49,6 +69,10 @@ const ReviewListParent = () => {

return (
<>
{/**
* #A402 접근성: 리뷰 목록 랜드마크 및 헤딩 구조 개선
* #U403 사용자 경험: 로딩 상태 스켈레톤 UI 적용
*/}
{flatData.length > 0 ? (
<>
<ReviewCardList reviews={flatData} />
Expand All @@ -61,7 +85,11 @@ const ReviewListParent = () => {
</div>
</>
) : (
<div className="flex min-h-40 w-full items-center justify-center">
<div
className="flex min-h-40 w-full items-center justify-center"
role="status"
aria-label="리뷰 없음"
>
<p className="text-sm font-medium text-gray-500">
아직 리뷰가 없어요
</p>
Expand Down
37 changes: 35 additions & 2 deletions src/app/favorites/_components/FavoriteList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"use client";

/**
* #P501 성능: Zustand 스토어 선택자 메모이제이션
* #T501 타입: Zustand 스토어 타입 안정성 강화
* #E601 에러: 데이터 로딩 실패 시 에러 처리
*/
import { useEffect, useState } from "react";

import MainCardItem from "@/components/layout/MainCardItem";
Expand All @@ -9,16 +14,30 @@ import useFavoriteStore from "@/stores/useFavoriteStore";
import useTabStore from "@/stores/useTabStore";
import { IGathering } from "@/types/gathering";

/**
* #P502 성능: 필터링 로직 최적화
* #A501 접근성: 필터링 및 정렬 컨트롤 접근성 개선
* #U501 사용자 경험: 필터 변경 시 애니메이션 추가
*/
const FavoriteList = () => {
/** #T502 타입: 스토어 상태 타입 명시적 정의 */
const tabIdxs = useTabStore(state => state.tabIdxs);
const favoriteList = useFavoriteStore(state => state.favoriteList);

const [filter, setFilter] = useState<string>("");

/**
* #P503 성능: 데이터 페칭 및 캐싱 전략 최적화
* #E602 에러: 데이터 로딩 상태 처리 개선
*/
const { data: favoriteData } = useSuspenseFavoriteList(filter, {
id: favoriteList,
});

/**
* #P504 성능: 클라이언트 사이드 필터링 최적화
* #E603 에러: 필터 상태 초기화 오류 처리
*/
useEffect(() => {
if (typeof window !== "undefined") {
setFilter(favoriteFilterKey[tabIdxs.join("-")]);
Expand All @@ -27,13 +46,27 @@ const FavoriteList = () => {

return (
<>
<div className="mb-8 flex flex-col items-center justify-center gap-6">
{/**
* #A502 접근성: 리스트 구조 및 랜드마크 개선
* #U502 사용자 경험: 로딩 및 빈 상태 UX 개선
*/}
<div
className="mb-8 flex flex-col items-center justify-center gap-6"
role="region"
aria-label="찜한 모임 목록"
>
{favoriteData &&
favoriteData.map((card: IGathering) => (
<MainCardItem key={card.id} gathering={card} />
))}
{favoriteData?.length === 0 && (
<p className="mt-28 text-gray-500">{"아직 찜한 모임이 없어요."}</p>
<p
className="mt-28 text-gray-500"
role="status"
aria-label="찜한 모임 없음"
>
{"아직 찜한 모임이 없어요."}
</p>
)}
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"use client";

/**
* #P301 성능: 모달 컴포넌트 지연 로딩 적용
* #T301 타입: Zustand 스토어 타입 안정성 강화
* #U301 사용자 경험: 모달 애니메이션 개선
*/
import { useRouter } from "next/navigation";
import { FaPlus } from "react-icons/fa";

Expand All @@ -9,22 +14,35 @@ import { Button } from "@/components/ui/Button";
import { useModal } from "@/hooks/useModal";
import useUserStore from "@/stores/useUserStore";

/**
* #A301 접근성: 모달 키보드 탐색 및 포커스 관리 개선
* #U302 사용자 경험: 모달 외부 클릭 시 확인 프롬프트 추가
* #E401 에러: 로그인 상태 변경 시 에러 처리
*/
const CreateGatheringModalWrapper = () => {
const router = useRouter();
const { isOpen, onClose, onOpen } = useModal();
const user = useUserStore(state => state.user);

return (
<>
{/**
* #A302 접근성: 반응형 레이아웃에서 버튼 레이블 관리
* #U303 사용자 경험: 버튼 호버/포커스 상태 개선
*/}
<Button
variant="purple"
className="h-10 w-10 rounded-full md:h-11 md:w-28 md:rounded-xl"
onClick={onOpen}
aria-label="모임 만들기"
>
<FaPlus className="md:hidden" />
<FaPlus className="md:hidden" aria-hidden="true" />
<span className="hidden md:block">모임 만들기</span>
</Button>
{/**
* #U304 사용자 경험: 모달 전환 애니메이션 추가
* #A303 접근성: 모달 ARIA 속성 및 역할 개선
*/}
{user ? (
<CreateGatheringModal isOpen={isOpen} onClose={onClose} />
) : (
Expand Down
Loading