-
Notifications
You must be signed in to change notification settings - Fork 3
Fix/pr #265
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
Fix/pr #265
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/app/university/[id]/InfoSection.tsx (3)
47-67: 1) 접힘/펼침 헤더 라벨이 서로 뒤바뀌었습니다. 즉시 교체해주세요.접힘 상태는 "자격요건"인데, 펼침 상태 헤더가 "기숙사"로 표기됩니다. 사용자 혼란이 큽니다.
- 접힘 블록(라인 47-67)은 OK입니다.
- 펼침 블록(라인 78-86)의 라벨을 "자격요건"으로 바꾸세요.
아래 패치를 적용하세요.
- <span className="text-base font-semibold text-k-900">기숙사</span> + <span className="text-base font-semibold text-k-900">자격요건</span>Also applies to: 78-86
123-131: 2) 숙소 섹션도 접힘/펼침 라벨이 반대로 표기됩니다. 일관되게 "기숙사"로 맞추세요.숙소 섹션은 접힘 상태에서 "기숙사"인데, 펼침 상태에서 "자격요건"으로 바뀝니다.
- 펼침 블록(라인 123-131)의 라벨을 "기숙사"로 수정하세요.
아래 패치를 적용하세요.
- <span className="text-base font-semibold text-k-900">자격요건</span> + <span className="text-base font-semibold text-k-900">기숙사</span>
147-149: 4) SVG 속성은 JSX 표기(camelCase)로 변경해야 TS/JSX 경고를 피합니다.
stroke-linecap,stroke-linejoin,fill-rule,clip-rule는 React/TSX에서 각각strokeLinecap,strokeLinejoin,fillRule,clipRule로 써야 합니다.
- 빌드/린트 경고 예방과 타입 일치를 위해 모두 교체하세요.
아래 패치를 적용하세요.
- <path d="M9 13.5L15 19.5L21 13.5" stroke="#5950F6" stroke-linecap="round" stroke-linejoin="round" /> + <path d="M9 13.5L15 19.5L21 13.5" stroke="#5950F6" strokeLinecap="round" strokeLinejoin="round" /> ... - <path d="M21 19.5L15 13.5L9 19.5" stroke="#5950F6" stroke-linecap="round" stroke-linejoin="round" /> + <path d="M21 19.5L15 13.5L9 19.5" stroke="#5950F6" strokeLinecap="round" strokeLinejoin="round" /> ... - <path - fill-rule="evenodd" - clip-rule="evenodd" + <path + fillRule="evenodd" + clipRule="evenodd" ... - <path - fill-rule="evenodd" - clip-rule="evenodd" + <path + fillRule="evenodd" + clipRule="evenodd"Also applies to: 155-156, 177-179, 191-195
src/app/community/[boardCode]/[postId]/PostPageContent.tsx (1)
27-30: 2) 에러 시 무한 스피너 가능성 제거가 필요합니다.
isLoading이 false인데 post가 null이면 계속 스피너가 보이는 흐름입니다. 간단히 가드를 분리하세요.아래 패치로 스피너-에러 분기만 정리합니다. 이후 에러 화면은 추후 보강해도 됩니다.
- if (isLoading || post === null) { - return <CloudSpinnerPage />; - } + if (isLoading) { + return <CloudSpinnerPage />; + } + if (post === null) { + // TODO: 에러 전용 화면으로 대체 + return null; + }src/app/community/[boardCode]/[postId]/KebabMenu.tsx (1)
29-37: 1) SVG 속성 카멜케이스 미적용으로 TSX 경고/미반영 위험.
stroke-width 등 하이픈 표기 대신 strokeWidth 표기가 필요합니다.- stroke="#B33BD4" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + stroke="#B33BD4" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round"
🧹 Nitpick comments (8)
src/app/university/[id]/InfoSection.tsx (1)
19-20: 3) 상태 변수 오탈자 정리로 가독성과 오용을 방지하세요.변수명이
detailsForAccomodationFold(오탈자)로 선언되어 있고 setter는setDetailsForAccommodationFold로 올바르게 표기되어 있습니다.
- 선언부와 사용부의 변수명을
detailsForAccommodationFold로 통일하세요.- IDE 리네임으로 전체 치환을 권장합니다.
아래 예시 패치를 참고하세요.
- const [detailsForAccomodationFold, setDetailsForAccommodationFold] = useState<boolean>(true); + const [detailsForAccommodationFold, setDetailsForAccommodationFold] = useState<boolean>(true); ... - {detailsForAccomodationFold ? ( + {detailsForAccommodationFold ? (Also applies to: 93-95
src/app/university/[id]/UniversityDetail.tsx (1)
2-2: 2) 사용되지 않는 Link import를 제거해 트리쉐이킹/린트 경고를 줄입시다.현재 파일 내
Link는 참조되지 않습니다.
- 불필요 import는 빌드 로그 노이즈를 유발합니다.
아래 패치를 적용하세요.
-import Link from "next/link";src/app/university/[id]/UniversityBtns.tsx (2)
38-46: 2) className 문자열 내 주석 토큰을 제거해 불필요한 DOM 클래스 누적을 방지하세요.
/* ... */주석이 문자열에 그대로 포함되어 실제 class로 전달됩니다.
- DOM 클래스 오염과 Tailwind JIT 스캔 노이즈를 줄이기 위해 제거하세요.
아래 패치를 적용하세요.
- <button - onClick={() => postUniversityFavorite(universityId)} - className={`/* stroke: #FFF; stroke-width: 1px; */ /* fill: linear-gradient(...) */ /* CSS의 fill은 SVG 속성이지만, 버튼 배경으로 적용합니다. */ /* backdrop-filter: blur(2px); */ /* filter: drop-shadow(...) */ /* 기타 스타일 */ rounded-full border border-white/80 bg-[linear-gradient(136deg,rgba(255,255,255,0.4)_14.87%,rgba(199,212,250,0.8)_89.1%)] p-3 drop-shadow-[2px_2px_6px_#C7D4FA] backdrop-blur-[2px] transition-transform duration-200 ease-in-out hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/50 active:scale-95`} - > + <button + onClick={() => postUniversityFavorite(universityId)} + className="rounded-full border border-white/80 bg-[linear-gradient(136deg,rgba(255,255,255,0.4)_14.87%,rgba(199,212,250,0.8)_89.1%)] p-3 drop-shadow-[2px_2px_6px_#C7D4FA] backdrop-blur-[2px] transition-transform duration-200 ease-in-out hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/50 active:scale-95" + > ... - <button - onClick={handleCopy} - className={`/* stroke: #FFF; stroke-width: 1px; */ /* fill: linear-gradient(...) */ /* CSS의 fill은 SVG 속성이지만, 버튼 배경으로 적용합니다. */ /* backdrop-filter: blur(2px); */ /* filter: drop-shadow(...) */ /* 기타 스타일 */ rounded-full border border-white/80 bg-[linear-gradient(136deg,rgba(255,255,255,0.4)_14.87%,rgba(199,212,250,0.8)_89.1%)] p-3 drop-shadow-[2px_2px_6px_#C7D4FA] backdrop-blur-[2px] transition-transform duration-200 ease-in-out hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/50 active:scale-95`} - > + <button + onClick={handleCopy} + className="rounded-full border border-white/80 bg-[linear-gradient(136deg,rgba(255,255,255,0.4)_14.87%,rgba(199,212,250,0.8)_89.1%)] p-3 drop-shadow-[2px_2px_6px_#C7D4FA] backdrop-blur-[2px] transition-transform duration-200 ease-in-out hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/50 active:scale-95" + >Also applies to: 45-47
29-31: 3) 연속 클릭 방지를 위해 요청 중에는 좋아요 버튼을 비활성화하세요.
useMutation의isPending으로 간단히 제어할 수 있습니다.
- 중복 요청과 토스트/알림 중복을 예방합니다.
아래 패치를 적용하세요.
- const { mutate: postUniversityFavorite } = usePostUniversityFavorite(); + const { mutate: postUniversityFavorite, isPending } = usePostUniversityFavorite(); ... - <button - onClick={() => postUniversityFavorite(universityId)} + <button + disabled={isPending} + onClick={() => postUniversityFavorite(universityId)} className="rounded-full border border-white/80 bg-[linear-gradient(136deg,rgba(255,255,255,0.4)_14.87%,rgba(199,212,250,0.8)_89.1%)] p-3 drop-shadow-[2px_2px_6px_#C7D4FA] backdrop-blur-[2px] transition-transform duration-200 ease-in-out hover:scale-110 focus:outline-none focus:ring-2 focus:ring-white/50 active:scale-95" >Also applies to: 38-46
src/app/community/[boardCode]/[postId]/PostPageContent.tsx (1)
55-66: 3) 인증 오류 리다이렉트 방식 일관화 검토.
document.location.href는 풀 리로드를 유발합니다. App Router 일관성을 위해 router.replace 사용 전환을 고려해 주세요.원하시면 해당 부분을 useFetchPost 내부에서 router 사용 가능하도록 리팩터 제안 드립니다.
src/app/community/[boardCode]/[postId]/KebabMenu.tsx (1)
11-24: 3) 외부 클릭 훅의 handler 재생성으로 인한 리스너 재바인딩 최소화.
콜백을 useCallback으로 고정하면 불필요한 add/remove를 줄일 수 있습니다.-import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useCallback } from "react"; @@ - useClickOutside(dropdownRef, () => { - setIsDropdownOpen(false); - }); + useClickOutside( + dropdownRef, + useCallback(() => { + setIsDropdownOpen(false); + }, []) + );src/api/community/client/useDeletePost.ts (2)
37-40: 2) useMutation 제네릭 지정으로 타입 안정성 보강.
응답/에러/변수 타입을 명시하면 이후 onSuccess에서 variables 사용이 안전해집니다.(위 패치에 포함되어 있습니다.)
46-48: 3) 상세 캐시 사용 시 삭제 후 상세 캐시 제거 검토.
키가 [QueryKeys.posts, postId] 형태라면 removeQueries(exact: true) 추가를 권장합니다.위 메인 패치에 함께 반영했습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
.env(0 hunks)src/api/community/client/queryKey.ts(1 hunks)src/api/community/client/useDeletePost.ts(1 hunks)src/api/university/client/usePostUniversityFavorite.ts(1 hunks)src/app/community/[boardCode]/[postId]/KebabMenu.tsx(4 hunks)src/app/community/[boardCode]/[postId]/PostPageContent.tsx(1 hunks)src/app/university/[id]/InfoSection.tsx(3 hunks)src/app/university/[id]/UniversityBtns.tsx(1 hunks)src/app/university/[id]/UniversityDetail.tsx(3 hunks)
💤 Files with no reviewable changes (1)
- .env
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: manNomi
PR: solid-connection/solid-connect-web#242
File: src/components/ui/TabSelector.tsx:10-11
Timestamp: 2025-08-12T09:41:44.182Z
Learning: manNomi prefers to keep reusable UI components simple and focused on core functionality rather than adding many features. They don't want to over-engineer flexible/reusable UI components at the initial stage.
Learnt from: manNomi
PR: solid-connection/solid-connect-web#245
File: src/components/mentor/MentorChatCard/index.tsx:17-21
Timestamp: 2025-08-24T11:14:34.297Z
Learning: manNomi prefers not to receive accessibility suggestions or recommendations during code reviews.
🧬 Code graph analysis (2)
src/api/community/client/useDeletePost.ts (1)
src/utils/axiosInstance.ts (1)
axiosInstance(32-35)
src/api/university/client/usePostUniversityFavorite.ts (2)
src/types/university.ts (1)
UniversityFavoriteResponse(87-89)src/utils/axiosInstance.ts (1)
axiosInstance(32-35)
🔇 Additional comments (3)
src/app/university/[id]/UniversityDetail.tsx (1)
25-27: 1) 상단 액션 버튼 배치가 자연스럽고 의도에 맞습니다.히어로 이미지 위 절대배치로 좋아요/복사 버튼을 노출하는 구성은 직관적입니다.
UniversityBtns가 프래그먼트를 반환하므로 두 버튼이 플렉스 컨테이너의 자식으로 올바르게 렌더링됩니다.- 현 구조는 의도한 좌우 분산 배치에 부합합니다.
src/api/community/client/queryKey.ts (1)
1-3: 1) 커뮤니티 쿼리 키 중심화, 좋아요.
단일 소스의 QueryKeys.posts 도입은 쿼리 무효화 일관성에 도움 됩니다.src/app/community/[boardCode]/[postId]/PostPageContent.tsx (1)
38-38: 1) KebabMenu에 boardCode/isOwner 전달, 연결 정상입니다.
Props 체인이 명확해졌고, 수정/삭제 가드(isOwner)가 의도대로 동작할 조건이 갖춰졌습니다.
| // 게시글 목록 페이지 이동 | ||
| router.replace("/community/FREE"); | ||
| }, |
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.
1) 삭제 후 경로 하드코딩(FREE)으로 보드 컨텍스트 이탈.
여러 보드에서 삭제 시 항상 FREE로 이동하는 것은 UX/흐름 상 오류에 가깝습니다. 호출자가 목적지(redirectTo)를 결정하도록 훅을 확장하세요. (아래 패치와 KebabMenu 패치 세트)
-const useDeletePost = () => {
+const useDeletePost = (options?: { redirectTo?: string }) => {
const router = useRouter();
const queryClient = useQueryClient();
- return useMutation({
+ return useMutation<AxiosResponse<DeletePostResponse>, unknown, number>({
@@
- onSuccess: () => {
+ onSuccess: (_data, variables) => {
// 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여
// 게시글 목록을 다시 불러오도록 합니다.
// ['posts', 'list'] 등 구체적인 키를 사용하셔도 좋습니다.
queryClient.invalidateQueries({ queryKey: [QueryKeys.posts] });
+ // 상세 캐시가 있다면 제거합니다. (키 구조 사용 시)
+ queryClient.removeQueries({ queryKey: [QueryKeys.posts, variables], exact: true });
alert("게시글이 성공적으로 삭제되었습니다.");
// 게시글 목록 페이지 이동
- router.replace("/community/FREE");
+ router.replace(options?.redirectTo ?? "/community/FREE");
},📝 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.
| // 게시글 목록 페이지 이동 | |
| router.replace("/community/FREE"); | |
| }, | |
| const useDeletePost = (options?: { redirectTo?: string }) => { | |
| const router = useRouter(); | |
| const queryClient = useQueryClient(); | |
| return useMutation<AxiosResponse<DeletePostResponse>, unknown, number>({ | |
| // ... your existing mutationFn / other options ... | |
| onSuccess: (_data, variables) => { | |
| // 'posts' 쿼리 키를 가진 모든 쿼리를 무효화하여 | |
| // 게시글 목록을 다시 불러오도록 합니다. | |
| queryClient.invalidateQueries({ queryKey: [QueryKeys.posts] }); | |
| // 상세 캐시가 있다면 제거합니다. (키 구조 사용 시) | |
| queryClient.removeQueries({ queryKey: [QueryKeys.posts, variables], exact: true }); | |
| alert("게시글이 성공적으로 삭제되었습니다."); | |
| // 게시글 목록 페이지 이동 (caller-supplied or default to FREE) | |
| router.replace(options?.redirectTo ?? "/community/FREE"); | |
| }, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In src/api/community/client/useDeletePost.ts around lines 50 to 52, the hook
currently hardcodes router.replace("/community/FREE") after deletion which
breaks board context; modify the hook signature to accept an optional redirectTo
string (or { redirectTo?: string }) parameter with a sensible default (e.g.,
current board path or "/community") and replace the hardcoded route call with
router.replace(redirectTo). Update the hook's TypeScript types/exports
accordingly and ensure callers (e.g., KebabMenu) pass the desired redirectTo
when invoking the hook; keep backward compatibility by falling back to the
default if none provided.
| interface UniversityFavoriteResponse { | ||
| universityInfoForApplyId: number; | ||
| message: string; | ||
| } |
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.
💡 Verification agent
🛠️ Refactor suggestion
🧩 Analysis chain
1) 응답 타입을 단일 출처로 통일하고, 뮤테이션 제네릭/메시지를 정교화하세요.
중복/상이한 응답 타입 정의가 있어 유지보수 리스크가 큽니다.
-
src/types/university.ts의UniversityFavoriteResponse를 가져와 재사용하세요.
-
axios.post와useMutation에 제네릭을 지정해 타입 안전성을 높이세요.
-
- 서버의
result값(예: LIKE_SUCCESS/LIKE_CANCELED)에 따라 성공 메시지를 분기하세요.
- 서버의
-
AxiosError<{ message: string }>로 에러 타입을 명확히 하고 중복 기본값을 제거하세요.
아래 패치를 적용하세요.
-import { AxiosResponse } from "axios";
+import { AxiosError, AxiosResponse } from "axios";
...
-interface UniversityFavoriteResponse {
- universityInfoForApplyId: number;
- message: string;
-}
+import type { UniversityFavoriteResponse } from "@/types/university";
...
-export const postUniversityFavoriteApi = (
- universityInfoForApplyId: number,
-): Promise<AxiosResponse<UniversityFavoriteResponse>> =>
- axiosInstance.post(`/univ-apply-infos/${universityInfoForApplyId}/like`);
+export const postUniversityFavoriteApi = (
+ universityInfoForApplyId: number,
+): Promise<AxiosResponse<UniversityFavoriteResponse>> =>
+ axiosInstance.post<UniversityFavoriteResponse>(`/univ-apply-infos/${universityInfoForApplyId}/like`);
...
- return useMutation({
+ return useMutation<AxiosResponse<UniversityFavoriteResponse>, AxiosError<{ message: string }>, number>({
// mutation 실행 시 호출될 함수
mutationFn: postUniversityFavoriteApi,
// mutation 성공 시 실행될 콜백
- onSuccess: () => {
+ onSuccess: (res) => {
// 위시리스트 관련 쿼리를 무효화하여 데이터를 다시 불러옵니다.
// ['favorites'], ['univ-apply-infos', 'like'] 등 구체적인 키를 사용하세요.
queryClient.invalidateQueries({ queryKey: ["favorites"] });
- alert("위시리스트에 학교를 추가했습니다.");
+ const result = res.data.result;
+ const msg = result === "LIKE_SUCCESS" ? "위시리스트에 학교를 추가했습니다." : "위시리스트에서 학교를 제거했습니다.";
+ alert(msg);
},
// mutation 실패 시 실행될 콜백
- onError: (error) => {
- const errorMessage = (error as any)?.response?.data?.message || "요청에 실패했습니다.";
- console.error("위시리스트 추가 실패:", errorMessage);
- alert(errorMessage || "요청에 실패했습니다. 잠시 후 다시 시도해주세요.");
+ onError: (error) => {
+ const errorMessage = error.response?.data?.message ?? "요청에 실패했습니다. 잠시 후 다시 시도해주세요.";
+ console.error("위시리스트 추가 실패:", errorMessage);
+ alert(errorMessage);
},
});추가로, 현재 키 ["favorites"]가 실제 조회 쿼리 키와 일치하는지 확인이 필요합니다.
원하시면 아래 스크립트로 레포 내 키 사용처를 점검해 드립니다.
참고: a11y 관련 제안은 과거 선호도에 따라 생략했습니다. 필요 시 별도로 다루겠습니다.
Also applies to: 22-26, 33-51
🏁 Script executed:
#!/bin/bash
# favorites 관련 queryKey 사용처 점검
rg -nP --type=ts --type=tsx -C2 '\bqueryKey\s*:\s*\[(?:\"favorites\"|\'favorites\')\]|\bQueryKeys\b|\binvalidateQueries\('Length of output: 305
리포지토리 전반에서 favorites 관련 쿼리 키와 invalidateQueries 사용처를 정확히 확인하기 위해, 아래 스크립트를 실행해 주세요.
- invalidateQueries 사용 위치 탐색
탭 안에서invalidateQueries호출이 어디에 있는지 확인합니다. - queryKey에
['favorites']사용 위치 탐색
queryKey 옵션으로favorites가 실제 사용된 곳을 찾아봅니다. - 전체 코드베이스에서
favorites문자열 탐색
혹시 다른 형태로 키가 쓰였는지 놓치지 않도록 전체 검색합니다.
🏁 Script executed:
#!/bin/bash
# 1) invalidateQueries 사용 위치 탐색
rg -n --type=ts --type=tsx "invalidateQueries"
# 2) queryKey에 'favorites' 사용 위치 탐색
rg -n --type=ts --type=tsx "queryKey" -C2 | rg "favorites"
# 3) 전체 코드베이스에서 'favorites' 문자열 탐색
rg -n "['\"]favorites['\"]" --type=ts --type=tsxLength of output: 242
리포지토리 전반에서 invalidateQueries, queryKey 옵션, 그리고 favorites 키 사용처를 다시 확인해 주세요. 아래 단계를 따라 실행해 주시면 됩니다.
- invalidateQueries 사용 위치 확인
탭 안에서 실제 호출된 모든invalidateQueries를 찾습니다. - queryKey에
favorites지정한 부분 확인
queryKey옵션을 사용하는 곳 중favorites가 포함된 부분을 찾습니다. - 전체 코드베이스에서
favorites문자열 탐색
혹시 다른 형태나 파일에서 키가 사용된 곳이 있는지 놓치지 않도록 전체 검색합니다.
🏁 Script executed:
#!/bin/bash
# 1) invalidateQueries 사용 위치 탐색
rg -n "invalidateQueries" src
# 2) queryKey 옵션에 'favorites' 사용 위치 탐색
rg -n "queryKey" -C2 src | rg "favorites"
# 3) 전체 코드베이스에서 'favorites' 문자열 탐색
rg -n "favorites" srcLength of output: 2615
필수: invalidateQueries 키를 실제 QueryKeys와 일치하도록 수정하고 타입 안전성을 강화하세요.
리포지토리 조사 결과, “favorites”라는 문자열 키는 오직 usePostUniversityFavorite.ts에서만 사용되고 있었습니다.
즐겨찾기 조회 쿼리에는 QueryKeys.univApplyInfosLike가 실제로 사용되므로, invalidateQueries의 queryKey를 이 값으로 교체해야 합니다.
- QueryKeys import 추가
import 구문에 QueryKeys를 가져와야 합니다. - invalidateQueries 키 수정
queryClient.invalidateQueries의 queryKey를["favorites"]에서[QueryKeys.univApplyInfosLike]로 변경합니다. - 불필요한 주석 제거
코드 내 “['favorites'], ['univ-apply-infos', 'like'] 등…” 주석을 삭제해 가독성을 높입니다.
-import { AxiosError, AxiosResponse } from "axios";
+import { AxiosError, AxiosResponse } from "axios";
+import { QueryKeys } from "@/api/queryKeys";
import type { UniversityFavoriteResponse } from "@/types/university";
export const postUniversityFavoriteApi = (
universityInfoForApplyId: number,
): Promise<AxiosResponse<UniversityFavoriteResponse>> =>
axiosInstance.post<UniversityFavoriteResponse>(`/univ-apply-infos/${universityInfoForApplyId}/like`);
export const usePostUniversityFavorite = () => {
return useMutation<
AxiosResponse<UniversityFavoriteResponse>,
AxiosError<{ message: string }>,
number
>({
mutationFn: postUniversityFavoriteApi,
onSuccess: (res) => {
- queryClient.invalidateQueries({ queryKey: ["favorites"] });
+ queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] });
const result = res.data.result;
alert(
result === "LIKE_SUCCESS"
? "위시리스트에 학교를 추가했습니다."
: "위시리스트에서 학교를 제거했습니다."
);
},
onError: (error) => {
const errorMessage =
error.response?.data?.message ??
"요청에 실패했습니다. 잠시 후 다시 시도해주세요.";
console.error("위시리스트 추가 실패:", errorMessage);
alert(errorMessage);
},
});
}📝 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.
| interface UniversityFavoriteResponse { | |
| universityInfoForApplyId: number; | |
| message: string; | |
| } | |
| // File: src/api/university/client/usePostUniversityFavorite.ts | |
| import { useMutation, useQueryClient } from "react-query"; | |
| import axiosInstance from "@/api/axiosInstance"; | |
| import { AxiosError, AxiosResponse } from "axios"; | |
| import { QueryKeys } from "@/api/queryKeys"; | |
| import type { UniversityFavoriteResponse } from "@/types/university"; | |
| export const postUniversityFavoriteApi = ( | |
| universityInfoForApplyId: number, | |
| ): Promise<AxiosResponse<UniversityFavoriteResponse>> => | |
| axiosInstance.post<UniversityFavoriteResponse>( | |
| `/univ-apply-infos/${universityInfoForApplyId}/like` | |
| ); | |
| export const usePostUniversityFavorite = () => { | |
| const queryClient = useQueryClient(); | |
| return useMutation< | |
| AxiosResponse<UniversityFavoriteResponse>, | |
| AxiosError<{ message: string }>, | |
| number | |
| >({ | |
| mutationFn: postUniversityFavoriteApi, | |
| onSuccess: (res) => { | |
| // Invalidate using the centralized key instead of the literal "favorites" | |
| queryClient.invalidateQueries({ queryKey: [QueryKeys.univApplyInfosLike] }); | |
| const result = res.data.result; | |
| alert( | |
| result === "LIKE_SUCCESS" | |
| ? "위시리스트에 학교를 추가했습니다." | |
| : "위시리스트에서 학교를 제거했습니다." | |
| ); | |
| }, | |
| onError: (error) => { | |
| const errorMessage = | |
| error.response?.data?.message ?? | |
| "요청에 실패했습니다. 잠시 후 다시 시도해주세요."; | |
| console.error("위시리스트 추가 실패:", errorMessage); | |
| alert(errorMessage); | |
| }, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In src/api/university/client/usePostUniversityFavorite.ts around lines 12 to 15,
replace the hardcoded invalidateQueries key and improve types: add an import for
QueryKeys at top of the file, change queryClient.invalidateQueries from using
["favorites"] to [QueryKeys.univApplyInfosLike], and remove the obsolete comment
mentioning "['favorites'], ['univ-apply-infos', 'like'] 등…" to keep the code
clean and type-safe.
| const KebabMenu = ({ postId, boardCode, isOwner = false }: KebabMenuProps) => { | ||
| const dropdownRef = useRef<HTMLDivElement>(null); | ||
| const { mutate: deletePost } = useDeletePost(); | ||
| const router = useRouter(); | ||
|
|
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.
💡 Verification agent
🧩 Analysis chain
2) 삭제 후 이동 경로는 보드 컨텍스트를 따라야 합니다.
현재 훅이 /community/FREE로 고정 이동합니다. 메뉴는 boardCode를 알고 있으니 훅에 redirectTo를 넘겨 보드별로 이동하세요. (훅 변경 패치와 세트)
-const KebabMenu = ({ postId, boardCode, isOwner = false }: KebabMenuProps) => {
+const KebabMenu = ({ postId, boardCode, isOwner = false }: KebabMenuProps) => {
const dropdownRef = useRef<HTMLDivElement>(null);
- const { mutate: deletePost } = useDeletePost();
+ const { mutate: deletePost } = useDeletePost({ redirectTo: `/community/${boardCode}` });
const router = useRouter();아래 스크립트로 useDeletePost 훅의 정의와 사용처를 찾고, redirectTo 옵션을 지원하는지 확인해 보겠습니다.
🏁 Script executed:
#!/bin/bash
# useDeletePost 훅 구현부 및 호출부 검색
rg -n 'useDeletePost' -C3 --glob '*.ts' --glob '*.tsx'Length of output: 2028
다음 스크립트로 useDeletePost 훅의 전체 구현부를 확인해 보겠습니다.
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' src/api/community/client/useDeletePost.tsLength of output: 1572
필수 리팩토링: 게시글 삭제 후 보드 컨텍스트에 맞춰 리다이렉트 경로를 동적으로 설정해주세요.
현재 useDeletePost 훅은 삭제 성공 시 항상 ‘/community/FREE’로 고정 이동합니다.
메뉴 컴포넌트에서 전달받은 boardCode를 활용해 해당 보드로 이동하도록 훅과 호출부를 함께 수정해야 합니다.
변경 사항은 아래와 같습니다.
-
useDeletePost 훅 변경
- 훅이redirectTo옵션을 파라미터로 받도록 함수 시그니처를 수정하세요.
- onSuccess 콜백에서router.replace(redirectTo)를 호출하도록 바꿔주세요. -
KebabMenu 호출부 수정
-const { mutate: deletePost } = useDeletePost();대신
const { mutate: deletePost } = useDeletePost({ redirectTo: \/community/${boardCode}` });` 로 넘겨주세요. -
타입 정의 보강
- useDeletePost 훅의 옵션 파라미터 타입에redirectTo?: string필드를 추가하세요.
이렇게 하면 게시글 삭제 후 각 보드별 컨텍스트에 맞춰 올바른 경로로 리다이렉트됩니다.
🤖 Prompt for AI Agents
In src/app/community/[boardCode]/[postId]/KebabMenu.tsx around lines 47 to 51,
the delete hook is currently called without a redirect option causing deleted
posts to always redirect to /community/FREE; update the hook usage to pass the
current board context by calling useDeletePost({ redirectTo:
`/community/${boardCode}` }) instead of useDeletePost(), and then update the
useDeletePost implementation signature to accept an options object with
redirectTo?: string, ensure the onSuccess handler uses
router.replace(redirectTo) (or falls back to existing behavior if undefined),
and add the redirectTo?: string field to the hook’s options type definition.
| const handleCopy = () => { | ||
| alert("URL이 복사되었습니다."); | ||
| navigator.clipboard.writeText(window.location.href).then(() => {}); | ||
| }; |
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
1) 복사 성공 후 알림 순서로 바꾸고 실패 처리도 추가하세요.
지금은 알림을 먼저 띄우고 복사를 시도합니다.
- 성공 시에만 알리고, 실패 시 사용자에게 안내하세요.
아래 패치를 적용하세요.
- const handleCopy = () => {
- alert("URL이 복사되었습니다.");
- navigator.clipboard.writeText(window.location.href).then(() => {});
- };
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(window.location.href);
+ alert("URL이 복사되었습니다.");
+ } catch {
+ alert("URL 복사에 실패했습니다. 잠시 후 다시 시도해주세요.");
+ }
+ };📝 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.
| const handleCopy = () => { | |
| alert("URL이 복사되었습니다."); | |
| navigator.clipboard.writeText(window.location.href).then(() => {}); | |
| }; | |
| const handleCopy = async () => { | |
| try { | |
| await navigator.clipboard.writeText(window.location.href); | |
| alert("URL이 복사되었습니다."); | |
| } catch { | |
| alert("URL 복사에 실패했습니다. 잠시 후 다시 시도해주세요."); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In src/app/university/[id]/UniversityBtns.tsx around lines 32 to 35, the current
handler shows the success alert before attempting to write to the clipboard and
lacks failure handling; change it so the clipboard write is attempted first and
only on successful completion show the "URL이 복사되었습니다." alert, and on failure
show a clear error alert (e.g., "URL 복사에 실패했습니다. 다시 시도해주세요.") — implement this
by using the Promise returned by navigator.clipboard.writeText (or async/await
with try/catch) and add a catch branch to surface the failure to the user;
optionally handle missing navigator.clipboard by falling back to a failure
message.
관련 이슈
• resolves: #이슈 번호
작업 내용
1. fix: 자격요건 → 기숙사로 문구 수정 및 구글맵 API 키 수정
커밋 링크
2. feat: Kebab 메뉴에 수정하기, 삭제하기 기능 추가
커밋 링크
3. fix: Kebab 메뉴 수정
커밋 링크
4. feat: 학교 위시리스트 추가 및 주소 복사 기능 구현
커밋 링크