Conversation
…-Prog/Prog-fe into feature/search-page#100
* chore: 마이페이지 API 타입, 모킹 및 상태관리 설정 * fix: 핸들러 수정 * fix: 하드코딩된 백엔드 URL 제거
* feat: 마이페이지 프로필 및 카드 UI 컴포넌트 추가 * fix: 유저 프로필 스켈레톤 추가 및 피드백 반영 * fix: 피드백 반영 및 접근성 향상 * fix: 피드백 반영 및 접근성 향상 * fix: 의존성 파일 추가 * fix: 타입 안정성 향상 및 중앙정렬 커스텀 클래스 생성 * fix: 이넘타입에 익스포트문 추가 * fix: css 수정 및 상수 통일 * fix: css 수정
…eature/search-page#100
style: SearchEmpty 버튼의 최소 너비 수정
…eature/search-page#100
📝 WalkthroughWalkthrough검색 페이지와 관련된 여러 컴포넌트·훅·유틸을 추가·수정하는 PR입니다. 검색 입력 폼(SearchForm), 검색 섹션(SearchSection), 정렬 컴포넌트(SortGroup), 커뮤니티 리스트 컴포넌트(CommunitySection/CommunityList/CommunityItem)와 이를 지원하는 타입 정의, 날짜 포맷터(formatRelativeDate), 라우트(ROUTES.prompt), 서버-클라이언트 React Query 하이드레이션(page.tsx), 쿼리 파라미터를 조작하는 커스텀 훅(useQueryParams) 및 백엔드 페칭 계층(fetcher, queries/api/prompts, hooks/use-prompt-list) 변경이 포함되어 있습니다. 또한 일부 UI 클래스와 타입(PromptBase/PromptPage) 구조가 조정되었습니다. Sequence DiagramsequenceDiagram
participant User as "User"
participant SearchForm as "SearchForm"
participant useQueryParams as "useQueryParams"
participant NextRouter as "Next Router"
participant SearchPage as "Search Page (SSR)"
participant QueryClient as "QueryClient (SSR)"
participant SearchSection as "SearchSection (Client)"
participant useGetPrompts as "useGetPrompts (React Query)"
participant Fetcher as "fetcher / API"
participant CommunitySection as "CommunitySection"
User->>SearchForm: 검색어 입력 및 제출
SearchForm->>useQueryParams: setParam("q", value)
useQueryParams->>NextRouter: router.push(updatedURL)
Note right of NextRouter: URL 업데이트 (쿼리 반영)
NextRouter->>SearchSection: 클라이언트가 q로 렌더링
SearchSection->>useGetPrompts: 쿼리키 ["prompts","ALL", q]로 요청
useGetPrompts->>Fetcher: fetchPrompts(category, page, q)
Fetcher->>Fetcher: 응답 파싱 및 에러 핸들링
Fetcher-->>useGetPrompts: ApiResponse<PromptPage>
useGetPrompts-->>SearchSection: 페이지 데이터(무한 스크롤)
SearchSection->>CommunitySection: props로 prompts 전달
CommunitySection-->>User: 리스트 렌더링
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 상세 검토 의견잘된 점 (짧고 위트 있게)
개선 권장 항목 (문제, 원인, 개선안)
테스트 권장
참고 자료
짧게: 구조와 분리가 좋아요. 인증 우회, 예외 로깅, 상수화, 라우터 히스토리 제어 부분만 정리하면 배포 준비 OK입니다. 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (7)
app/(afterLogin)/(dashboard)/(community-category)/_components/community.tsx (3)
30-51: 더미 데이터 관리 방안API 스펙이 없어 더미 데이터를 사용 중인 것은 이해하지만, 나중에 실제 데이터로 교체 시 이 부분을 찾기 쉽도록 별도 파일이나 명확한 주석을 추가하는 것을 권장합니다.
// TODO: API 연동 후 제거 - Issue `#100` // `@see` https://api-docs.example.com/community const dummyData: CommunityPromptItem[] = [...]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/community.tsx around lines 30 - 51, Extract the inline dummyData array (typed as CommunityPromptItem) out of the component file and place it into a dedicated module (e.g., community dummy data module) that exports dummyData, and update the component to import it; in the new module add a clear TODO comment with an issue number and API docs link (e.g., "// TODO: remove after API integration - Issue `#100`" and a reference URL) so future removal/replacement is obvious—update any references to dummyData in the component to use the imported symbol.
22-26: 주석 처리된 코드 정리 필요검색 결과 유무에 따른 조건부 렌더링이 주석으로 처리되어 있습니다. 실제 구현 시 조건부 렌더링으로 전환하거나, 현재 단계에서 필요 없다면 제거해주세요.
export function CommunitySection({ isEmpty }: { isEmpty: boolean }) { if (isEmpty) { return ( <div className="py-5.5"> <SearchEmpty /> </div> ); } return ( <div className="border p-5 rounded-10 bg-white mt-7"> <CommunityList /> </div> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/community.tsx around lines 22 - 26, The commented-out conditional rendering for empty search results should be implemented or removed: update the CommunitySection component to accept the prop isEmpty and render the SearchEmpty block when isEmpty is true, otherwise render the existing CommunityList wrapper; locate the CommunitySection function and replace the commented JSX with a proper if (isEmpty) return (...) branch (using SearchEmpty) and the existing return for CommunityList, or if unused remove the commented lines entirely to keep the code clean.
1-2: 파일 상단 날짜/파일명 주석 제거 권장
community-type.ts와 동일한 패턴입니다. Git에서 추적되므로 불필요합니다.-// 2026-03-02 -// community.tsx "use client";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/community.tsx around lines 1 - 2, community.tsx contains redundant top-of-file comments with date and filename; remove those lines (the "// 2026-03-02" and "// community.tsx" comments) so the file follows the same pattern as community-type.ts, ensure no extra blank lines are left at the top, and commit the change.app/(afterLogin)/(dashboard)/_types/community-type.ts (2)
30-31:CommunityPrompt타입 별칭의 필요성 검토현재
CommunityPrompt는CommunityPromptItem과 완전히 동일합니다. 주석에는 "API 응답값으로 사용"이라고 되어 있는데, 실제 API 응답에 pagination이나 meta 정보가 추가될 예정이라면 다음과 같이 확장하는 것이 좋습니다:// API 응답 타입 (pagination 등 메타 정보 포함) export interface CommunityPromptResponse { items: CommunityPromptItem[]; totalCount: number; page: number; // ... }현재 상태로는 중복되어 혼란을 줄 수 있으니, API 스펙이 확정되면 실제 응답 구조에 맞게 수정하시면 됩니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/_types/community-type.ts around lines 30 - 31, The alias CommunityPrompt currently duplicates CommunityPromptItem; verify intended use and either remove the alias or replace it with a proper API response interface (e.g., CommunityPromptResponse) that wraps items: CommunityPromptItem[] plus pagination/meta fields like totalCount and page; update any consumers that import CommunityPrompt to use CommunityPromptItem (if alias removed) or CommunityPromptResponse (if API response shape is required) and adjust names in the module to avoid the confusing duplicate type.
1-2: 파일 상단 주석 제거 권장날짜와 파일명 주석은 버전 관리 시스템(Git)에서 자동으로 추적되므로 불필요합니다. 유지보수 시 outdated 정보가 될 수 있어요.
-// 2026-03-02 -// community-type.ts - // 커뮤니티 게시글 기본 타입🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/_types/community-type.ts around lines 1 - 2, Remove the redundant top-of-file comment lines ("// 2026-03-02" and "// community-type.ts") from community-type.ts; simply delete those two comment lines so the file starts with its actual TypeScript code (or exports/types) and commit the change with a clear message like "chore: remove outdated file header comments".app/hooks/use-query-parmas.tsx (1)
43-43:router.pushvsrouter.replace고려현재 모든 파라미터 변경에
router.push를 사용하고 있어서, 정렬 옵션 변경 같은 minor한 조작도 브라우저 히스토리에 쌓입니다.사용자가 뒤로가기 시 이전 정렬 상태로 돌아가길 원하면 현재 방식이 맞지만, 필터/정렬 변경은 히스토리에 쌓지 않는 것이 일반적인 UX입니다.
♻️ router.replace 옵션 추가 제안
const setParam = useCallback( (key: string, value: string, replace = false) => { const params = createParams(); params.set(key, value); const url = `${pathname}?${params.toString()}`; replace ? router.replace(url) : router.push(url); }, [createParams, pathname, router] );Also applies to: 55-55, 66-66, 77-77
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/hooks/use-query-parmas.tsx` at line 43, The current setParam implementations call router.push (e.g., the call at router.push(`${pathname}?${params.toString()}`) and similar calls at the other occurrences) which indiscriminately adds history entries; modify the setter(s) (the useCallback function(s) that call createParams, set the param, build url using pathname and params.toString()) to accept an optional replace boolean (default false) and call router.replace(url) when replace is true, otherwise router.push(url); update all occurrences (the calls at lines shown) and any callers to optionally pass replace for filter/sort changes that should not create history entries.app/(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsx (1)
31-51: 정렬 토글 버튼에 상태 접근성 속성을 추가해 주세요지금도 동작은 좋습니다(깔끔해요 👍). 다만 선택 상태를 스크린리더가 알 수 있도록
aria-pressed를 넣고, 안전하게type="button"을 명시하는 편이 좋습니다.수정 예시
- <button onClick={() => handleSortChange("latest")}> + <button + type="button" + aria-pressed={isActive("latest")} + onClick={() => handleSortChange("latest")} + > ... - <button onClick={() => handleSortChange("popular")}> + <button + type="button" + aria-pressed={isActive("popular")} + onClick={() => handleSortChange("popular")} + >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsx around lines 31 - 51, Add explicit type="button" and an aria-pressed attribute to each sort toggle button so screen readers can detect selection state; update the two <button> elements that call handleSortChange("latest") and handleSortChange("popular") to include type="button" and aria-pressed={isActive("latest")} and aria-pressed={isActive("popular")} respectively (use the existing isActive function to supply the boolean), leaving the cn(BASE_STYLE, isActive(...) ? ACTIVE_STYLE : INACTIVE_STYLE) logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/community.tsx:
- Around line 71-97: The Link currently wraps an interactive button (the <Link>
element that uses ROUTES.prompt.DETAIL(...) contains the <button> with
ThumbsUpIcon and like count), which breaks accessibility and causes navigation
when the button is clicked; fix by moving the like button out of the Link so the
Link only wraps the title (or by restructuring the container into a non-link
wrapper and placing a separate <Link> around the heading), ensure the like
button's click handler is on the standalone button (or if you must keep it
inside the Link temporarily, add e.preventDefault() and e.stopPropagation() in
the button's onClick) and keep other metadata (authorNickname,
formatRelativeDate(createdAt)) visually aligned with CSS after the refactor.
In
`@app/`(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsx:
- Around line 17-27: currentSort is taken with a raw type assertion from
getParam("sort") which allows invalid values (e.g. ?sort=foo) to produce no
active option; add a normalization/validation step that maps the raw string to a
proper SortType or falls back to "latest" (e.g. create a small normalizeSort
helper that checks the string against the allowed SortType values and returns
"latest" for anything else), use that normalized value in currentSort and in
isActive, and ensure handleSortChange / setParams continues to set only valid
SortType values (or omit the param for "latest") so the component never sees an
unexpected sort value.
In
`@app/`(afterLogin)/(dashboard)/(community-category)/search/_components/search-form.tsx:
- Around line 19-24: The handleSearch function currently builds the query by
concatenating search into `${pathname}?q=${search}`, which can produce unencoded
characters and overwrite existing query params; instead, read and clone the
existing searchParams (the searchParams variable), create a URLSearchParams
instance, set the "q" key to the trimmed search value (which will be URL-encoded
by URLSearchParams) and preserve other params, then navigate using router.push
with `${pathname}?${params.toString()}` (or pass the params as a query object)
so special characters are encoded and existing query parameters are retained;
update the handleSearch function and reference search, searchParams, pathname,
and router accordingly.
- Around line 25-41: Wrap the search controls in a semantic <form> and wire its
onSubmit to the existing handleSearch so Enter triggers searches: move the
IconInput and BaseButton into a <form onSubmit={handleSubmit}> (or set
onSubmit={handleSearch} if you update handleSearch to accept and call
event.preventDefault()), change the BaseButton to type="submit" (remove or keep
onClick as redundant), and ensure handleSearch calls event.preventDefault() when
used as a form handler; update references to IconInput, BaseButton,
handleSearch, search, and setSearch accordingly.
- Around line 14-15: The input state initialized with const [search, setSearch]
= useState(q || "") only captures the URL param on first render so the input can
drift when the URL's q changes (back/forward). Fix by adding an effect that
watches q and updates setSearch(q || "") whenever q changes (ensure you
reference search, setSearch and q in the component), so the controlled input
stays in sync with the URL; keep existing handlers for user typing unchanged.
In
`@app/`(afterLogin)/(dashboard)/(community-category)/search/_components/search-section.tsx:
- Line 15: The SearchTitle is being given q?.length (the search string length)
instead of the number of results; replace the searchLength prop usage with a
real results count (e.g., results.length or searchResults?.length ?? 0) when
rendering SearchTitle and/or add a resultCount prop so it receives the actual
number (default 0) rather than q?.length; update the render that calls
SearchTitle and adjust SearchTitle's props/type if needed to consume the new
result count prop.
In `@app/hooks/use-query-parmas.tsx`:
- Line 1: File name has a typo: rename the file from use-query-parmas.tsx to
use-query-params.tsx and update all imports referencing use-query-parmas to the
corrected name; ensure any export/import statements that reference the hook file
(e.g., imports like import useQueryParams from "app/hooks/use-query-parmas") are
updated to "app/hooks/use-query-params" and run a project-wide search to catch
all usages.
In `@components/search/search-empty.tsx`:
- Line 30: The class "min-w-49.25" used on the BaseButton is not a defined
Tailwind spacing token; update the className in the BaseButton JSX (where
BaseButton and handleClick are used) to use a valid Tailwind arbitrary value
like "min-w-[197px]" or alternatively add a custom spacing token in the theme
config and use that token; make the change in the component's className string
so the rendered button uses a valid Tailwind class.
In `@lib/format-date.ts`:
- Around line 15-40: formatRelativeDate currently doesn't detect Invalid Date
(new Date(...) returns an Invalid Date rather than throwing) and negative diffs
for future dates; validate target by checking isNaN(target.getTime()) and return
"날짜 오류" immediately if invalid, then compute diffMinutes/diffHours/diffDays and
clamp negative values to zero (e.g., diffMinutes = Math.max(0, diffMinutes)) so
future dates are treated consistently (e.g., "방금 전") before applying the
existing branches; reference function formatRelativeDate and variables target,
diffMinutes, diffHours, diffDays when making the changes.
In `@lib/routes.ts`:
- Around line 39-41: The CommunityItem component is using the wrong route
constant (ROUTES.prompt.DETAIL) which points to a nonexistent /prompt/[id] path;
update the usage to ROUTES.community.DETAIL so community items navigate to the
correct /community/[id] route. Locate the reference in CommunityItem (around the
component code that builds item links) and replace ROUTES.prompt.DETAIL with
ROUTES.community.DETAIL, ensuring any link construction or parameter
interpolation still uses the same id argument.
In `@proxy.ts`:
- Around line 10-11: isSignIn currently conflates real auth state with a dev
bypass (isSignIn = isDevelopment ? true : !!session), causing dev mode to appear
signed-in and breaking sign-in/sign-up flows; change this to compute a real
boolean isSignedIn = !!session and a separate dev bypass flag (e.g., devBypass =
isDevelopment && <optional dev-only condition>), then update the redirect logic
that uses isSignIn/isAuthRoute/isNewUser so it only treats a user as
authenticated when isSignedIn is true and only skips protection when devBypass
is explicitly enabled (i.e., use !isSignedIn && isAuthRoute && !devBypass for
redirect decisions).
---
Nitpick comments:
In `@app/`(afterLogin)/(dashboard)/_types/community-type.ts:
- Around line 30-31: The alias CommunityPrompt currently duplicates
CommunityPromptItem; verify intended use and either remove the alias or replace
it with a proper API response interface (e.g., CommunityPromptResponse) that
wraps items: CommunityPromptItem[] plus pagination/meta fields like totalCount
and page; update any consumers that import CommunityPrompt to use
CommunityPromptItem (if alias removed) or CommunityPromptResponse (if API
response shape is required) and adjust names in the module to avoid the
confusing duplicate type.
- Around line 1-2: Remove the redundant top-of-file comment lines ("//
2026-03-02" and "// community-type.ts") from community-type.ts; simply delete
those two comment lines so the file starts with its actual TypeScript code (or
exports/types) and commit the change with a clear message like "chore: remove
outdated file header comments".
In `@app/`(afterLogin)/(dashboard)/(community-category)/_components/community.tsx:
- Around line 30-51: Extract the inline dummyData array (typed as
CommunityPromptItem) out of the component file and place it into a dedicated
module (e.g., community dummy data module) that exports dummyData, and update
the component to import it; in the new module add a clear TODO comment with an
issue number and API docs link (e.g., "// TODO: remove after API integration -
Issue `#100`" and a reference URL) so future removal/replacement is obvious—update
any references to dummyData in the component to use the imported symbol.
- Around line 22-26: The commented-out conditional rendering for empty search
results should be implemented or removed: update the CommunitySection component
to accept the prop isEmpty and render the SearchEmpty block when isEmpty is
true, otherwise render the existing CommunityList wrapper; locate the
CommunitySection function and replace the commented JSX with a proper if
(isEmpty) return (...) branch (using SearchEmpty) and the existing return for
CommunityList, or if unused remove the commented lines entirely to keep the code
clean.
- Around line 1-2: community.tsx contains redundant top-of-file comments with
date and filename; remove those lines (the "// 2026-03-02" and "//
community.tsx" comments) so the file follows the same pattern as
community-type.ts, ensure no extra blank lines are left at the top, and commit
the change.
In
`@app/`(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsx:
- Around line 31-51: Add explicit type="button" and an aria-pressed attribute to
each sort toggle button so screen readers can detect selection state; update the
two <button> elements that call handleSortChange("latest") and
handleSortChange("popular") to include type="button" and
aria-pressed={isActive("latest")} and aria-pressed={isActive("popular")}
respectively (use the existing isActive function to supply the boolean), leaving
the cn(BASE_STYLE, isActive(...) ? ACTIVE_STYLE : INACTIVE_STYLE) logic
unchanged.
In `@app/hooks/use-query-parmas.tsx`:
- Line 43: The current setParam implementations call router.push (e.g., the call
at router.push(`${pathname}?${params.toString()}`) and similar calls at the
other occurrences) which indiscriminately adds history entries; modify the
setter(s) (the useCallback function(s) that call createParams, set the param,
build url using pathname and params.toString()) to accept an optional replace
boolean (default false) and call router.replace(url) when replace is true,
otherwise router.push(url); update all occurrences (the calls at lines shown)
and any callers to optionally pass replace for filter/sort changes that should
not create history entries.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
app/(afterLogin)/(dashboard)/(community-category)/_components/community.tsxapp/(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/_components/search-form.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/_components/search-section.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/page.tsxapp/(afterLogin)/(dashboard)/_types/community-type.tsapp/hooks/use-query-parmas.tsxcomponents/search/search-empty.tsxcomponents/search/search-title.tsxcomponents/shared/inputs.tsxlib/format-date.tslib/routes.tsproxy.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/hooks/use-query-params.tsx`:
- Around line 72-76: The forEach callback inside deleteParams currently uses an
expression body (keys.forEach((key) => params.delete(key))) which causes an
implicit return and violates Biome's useIterableCallbackReturn rule; change the
callback to a block body so it returns void (e.g., keys.forEach((key) => {
params.delete(key); })), keeping the rest of deleteParams and createParams usage
unchanged to avoid altering behavior.
In `@components/header/header-search.tsx`:
- Line 24: The current call to setParam("q", trimAndSanitizedValue(searchValue))
over-sanitizes queries and leaves an empty q param; change it to use only
trimming and remove the param when empty by calling setParam("q", trimmed) only
if trimmed !== "" (otherwise delete/remove the "q" param), stop using
trimAndSanitizedValue here (or replace with a local trimmed variable), and rely
on URLSearchParams/router for encoding; keep sanitization/XSS handling to output
encoding/server-side validation instead of mutating the search term in the
header-search component.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/(afterLogin)/(dashboard)/(community-category)/_components/sort-group.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/_components/search-form.tsxapp/hooks/use-query-params.tsxcomponents/header/header-search.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- app/(afterLogin)/(dashboard)/(community-category)/search/_components/search-form.tsx
fryzke
left a comment
There was a problem hiding this comment.
작업량 많으셨을텐데 수고 많으셨습니다! ㅜㅜ
확장성까지 고려된 깔끔한 코드였습니다. 👍
로컬에서도 실행해봤는데 페이지 문제 없이 잘 보였습니다!
변경된 라우트에 맞춰서 저도 Rank 페이지 프롬프트 클릭 시 페이지가 이동되도록 바로 작업해둘게요. 혹시 진행하시다가 추가로 수정하신 부분이 생기셨다면 알려주세요~!
|
아직 api 명세서가 제대로 나오지 않아서 prompt쪽은 .... 명세서가 아직 작성이 안되서 ... |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
hooks/use-prompt-list.ts (3)
129-131:⚠️ Potential issue | 🟡 Minor동일하게 에러 핸들러에서
toasts.error를 사용해야 합니다.🐛 수정 제안
onError: (error: Error) => { - toasts.success(error.message); + toasts.error(error.message); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/use-prompt-list.ts` around lines 129 - 131, The onError handler in the usePromptList hook currently calls toasts.success with the error message; change it to call toasts.error so errors are shown correctly. Locate the onError callback inside usePromptList (the function where onError: (error: Error) => { ... } is defined) and replace toasts.success(error.message) with toasts.error(error.message), keeping the error parameter and message unchanged.
58-86:⚠️ Potential issue | 🟠 MajorOptimistic update의 데이터 구조가 실제 응답 구조와 일치하지 않습니다.
useGetPrompts는ApiResponse<PromptPage>를 반환하므로, optimistic update에서page.items대신page.data.items에 접근해야 합니다. 현재 코드는 타입도InfiniteData<PromptPage>로 되어있어 실제 구조와 맞지 않습니다.🐛 수정 제안
const previousPrompts = queryClient.getQueriesData< - InfiniteData<PromptPage> + InfiniteData<ApiResponse<PromptPage>> >({ queryKey: ["prompts"], }); - queryClient.setQueriesData<InfiniteData<PromptPage>>( + queryClient.setQueriesData<InfiniteData<ApiResponse<PromptPage>>>( { queryKey: ["prompts"] }, (old) => { if (!old) return old; return { ...old, - pages: old.pages.map((page: PromptPage) => ({ + pages: old.pages.map((page: ApiResponse<PromptPage>) => ({ ...page, - items: page.items.map((item: PromptBase) => + data: { + ...page.data, + items: page.data.items.map((item: PromptBase) => - item.id === promptId + item.id === promptId - ? { + ? { ...item, isLiked: !isLiked, likes: isLiked ? Math.max((item.likes ?? 0) - 1, 0) : (item.likes ?? 0) + 1, } - : item + : item - ), + ), + }, })), }; } );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/use-prompt-list.ts` around lines 58 - 86, The optimistic update in the setQueriesData block assumes pages contain PromptPage directly but useGetPrompts returns ApiResponse<PromptPage>, so update the cache shape: read and restore previous data via queryClient.getQueriesData/getQueryData for the "prompts" key, treat each page as ApiResponse<PromptPage> (i.e., access page.data.items), and when toggling a prompt ID in queryClient.setQueriesData (called in your hook around useGetPrompts / setQueriesData), update page.data.items instead of page.items and keep the outer ApiResponse wrapper and types (replace InfiniteData<PromptPage> usage with InfiniteData<ApiResponse<PromptPage>> or the correct generic) so the cached structure matches the real response.
96-96:⚠️ Potential issue | 🟡 Minor에러 핸들러에서
toasts.success대신toasts.error를 사용해야 합니다.에러 상황에서 성공 토스트를 표시하면 사용자에게 혼란을 줍니다.
🐛 수정 제안
onError: (err, newLike, context) => { if (context?.previousPrompts) { context.previousPrompts.forEach(([key, data]) => { queryClient.setQueryData(key, data); }); } - toasts.success("좋아요 처리에 실패했습니다."); + toasts.error("좋아요 처리에 실패했습니다."); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/use-prompt-list.ts` at line 96, In the usePromptList hook, replace the incorrect success toast call with an error toast in the error handler: change the toasts.success("좋아요 처리에 실패했습니다.") call to toasts.error(...) where the failure is handled (look for the handler around functions like handleLike/toggleLike or the rejection/catch block inside usePromptList). Ensure the error toast carries the same message and is used wherever the "좋아요 처리에 실패했습니다." failure message is emitted.
🧹 Nitpick comments (2)
components/header/header-search.tsx (1)
10-13: 사용되지 않는trimAndSanitizedValue함수를 제거하세요.이전 리뷰 피드백을 반영하여
trim()만 사용하도록 개선되었지만, 기존trimAndSanitizedValue함수가 데드 코드로 남아있습니다.♻️ 제거 제안
-function trimAndSanitizedValue(value: string) { - // eslint-disable-next-line no-useless-escape - return value.trim().replace(/[<>\"\'&]/g, ""); -} -🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/header/header-search.tsx` around lines 10 - 13, Remove the dead helper function trimAndSanitizedValue from the header-search component: delete the function declaration and any associated unused imports/exports or references, and replace any call sites (if any remain) with value.trim() (the new intended behaviour). After removal, run TypeScript/ESLint to ensure no unused symbol or import warnings remain and update tests or typings that referenced trimAndSanitizedValue.lib/fetcher.ts (1)
9-15: 사용되지 않는PagedResponse<T>인터페이스 제거를 검토하세요.이 인터페이스는 코드베이스 어디에서도 참조되지 않고 있습니다. 향후 사용 계획이 없다면 제거하는 것이 좋습니다. 사용되지 않는 타입 정의는 코드 복잡도를 높이고 유지보수 비용을 증가시키므로, 필요할 때 충분히 시간을 두고 추가할 수 있습니다. 만약 나중에 필요하다면 git 히스토리에서 언제든 복구할 수 있으니 과감하게 정리해도 괜찮습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/fetcher.ts` around lines 9 - 15, The PagedResponse<T> interface is unused and should be removed to reduce dead code; delete the interface declaration named PagedResponse<T> from lib/fetcher.ts, run TypeScript checks (tsc) and your test suite to ensure no references remain, and commit the change with a brief message like "chore: remove unused PagedResponse<T> interface".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(afterLogin)/(dashboard)/(community-category)/search/page.tsx:
- Around line 19-25: You are calling the React hook useGetPrompts inside
queryClient.prefetchInfiniteQuery in the server component
(queryClient.prefetchInfiniteQuery, useGetPrompts) which is illegal and also
using a different queryKey than the client (server: ["prompts","search", q] vs
client in search-section.tsx: ["prompts","ALL", currentSearch]); replace the
hook call with the plain async data-fetcher (e.g., getPrompts / fetchPrompts —
the non-hook function that returns a Promise) as the queryFn and make the
queryKey identical to the client (or change the client key to match) so the
server-prefetched data is reused by the client; ensure queryFn accepts
pageParams and returns the same shape the client expects for infinite queries
(no React hooks used).
In `@lib/fetcher.ts`:
- Around line 73-79: The if-block checking errorData.error.errorClassName for
"TOKEN_EXPIRED"/"INVALID_TOKEN" is empty; implement token-expiry handling by
invoking your auth flow: call a refresh function (e.g., refreshAuthToken()) and,
if it succeeds, retry the original request, otherwise call logoutUser() (or emit
an auth failure event via authEventEmitter/onAuthFailure) and throw or return a
clear AuthError so callers can show UI feedback; place this logic inside the
existing if that references errorData.error.errorClassName and add a TODO
fallback if refresh endpoint is not yet available.
---
Outside diff comments:
In `@hooks/use-prompt-list.ts`:
- Around line 129-131: The onError handler in the usePromptList hook currently
calls toasts.success with the error message; change it to call toasts.error so
errors are shown correctly. Locate the onError callback inside usePromptList
(the function where onError: (error: Error) => { ... } is defined) and replace
toasts.success(error.message) with toasts.error(error.message), keeping the
error parameter and message unchanged.
- Around line 58-86: The optimistic update in the setQueriesData block assumes
pages contain PromptPage directly but useGetPrompts returns
ApiResponse<PromptPage>, so update the cache shape: read and restore previous
data via queryClient.getQueriesData/getQueryData for the "prompts" key, treat
each page as ApiResponse<PromptPage> (i.e., access page.data.items), and when
toggling a prompt ID in queryClient.setQueriesData (called in your hook around
useGetPrompts / setQueriesData), update page.data.items instead of page.items
and keep the outer ApiResponse wrapper and types (replace
InfiniteData<PromptPage> usage with InfiniteData<ApiResponse<PromptPage>> or the
correct generic) so the cached structure matches the real response.
- Line 96: In the usePromptList hook, replace the incorrect success toast call
with an error toast in the error handler: change the toasts.success("좋아요 처리에
실패했습니다.") call to toasts.error(...) where the failure is handled (look for the
handler around functions like handleLike/toggleLike or the rejection/catch block
inside usePromptList). Ensure the error toast carries the same message and is
used wherever the "좋아요 처리에 실패했습니다." failure message is emitted.
---
Nitpick comments:
In `@components/header/header-search.tsx`:
- Around line 10-13: Remove the dead helper function trimAndSanitizedValue from
the header-search component: delete the function declaration and any associated
unused imports/exports or references, and replace any call sites (if any remain)
with value.trim() (the new intended behaviour). After removal, run
TypeScript/ESLint to ensure no unused symbol or import warnings remain and
update tests or typings that referenced trimAndSanitizedValue.
In `@lib/fetcher.ts`:
- Around line 9-15: The PagedResponse<T> interface is unused and should be
removed to reduce dead code; delete the interface declaration named
PagedResponse<T> from lib/fetcher.ts, run TypeScript checks (tsc) and your test
suite to ensure no references remain, and commit the change with a brief message
like "chore: remove unused PagedResponse<T> interface".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ca8cf9a8-e68a-4ce9-b204-ae99e0a69290
📒 Files selected for processing (10)
app/(afterLogin)/(dashboard)/(community-category)/_components/community.tsxapp/(afterLogin)/(dashboard)/(community-category)/rank/RankList.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/_components/search-section.tsxapp/(afterLogin)/(dashboard)/(community-category)/search/page.tsxapp/hooks/use-query-params.tsxapp/types/type.tscomponents/header/header-search.tsxhooks/use-prompt-list.tslib/fetcher.tsqueries/api/prompts.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- app/(afterLogin)/(dashboard)/(community-category)/_components/community.tsx
- app/hooks/use-query-params.tsx
| if (q) { | ||
| await queryClient.prefetchInfiniteQuery({ | ||
| queryKey: ["prompts", "search", q], | ||
| queryFn: () => useGetPrompts(q), | ||
| initialPageParam: 0, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🚨 치명적 오류: React Hook을 서버 컴포넌트의 queryFn으로 사용하고 있습니다.
useGetPrompts는 React Hook입니다. 서버 컴포넌트에서 queryFn으로 호출하면 런타임 에러가 발생합니다. 또한 쿼리 키 불일치 문제가 있습니다:
- 서버 prefetch:
["prompts", "search", q] - 클라이언트 (
search-section.tsx):["prompts", "ALL", currentSearch]
이로 인해 서버에서 prefetch한 데이터가 클라이언트에서 재사용되지 않습니다.
🐛 수정 제안
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
-import { useGetPrompts } from "@/hooks/use-prompt-list";
+import { fetchPrompts } from "@/queries/api/prompts";
import { SearchSection } from "./_components/search-section";
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string }>;
}) {
const { q } = await searchParams;
const queryClient = new QueryClient();
if (q) {
await queryClient.prefetchInfiniteQuery({
- queryKey: ["prompts", "search", q],
- queryFn: () => useGetPrompts(q),
+ queryKey: ["prompts", "ALL", q],
+ queryFn: ({ pageParam = 0 }) => fetchPrompts("ALL", pageParam as number, q),
initialPageParam: 0,
});
}
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<SearchSection q={q} />
</HydrationBoundary>
);
}참고: TanStack Query - SSR Guide
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/`(afterLogin)/(dashboard)/(community-category)/search/page.tsx around
lines 19 - 25, You are calling the React hook useGetPrompts inside
queryClient.prefetchInfiniteQuery in the server component
(queryClient.prefetchInfiniteQuery, useGetPrompts) which is illegal and also
using a different queryKey than the client (server: ["prompts","search", q] vs
client in search-section.tsx: ["prompts","ALL", currentSearch]); replace the
hook call with the plain async data-fetcher (e.g., getPrompts / fetchPrompts —
the non-hook function that returns a Promise) as the queryFn and make the
queryKey identical to the client (or change the client key to match) so the
server-prefetched data is reused by the client; ensure queryFn accepts
pageParams and returns the same shape the client expects for infinite queries
(no React hooks used).
| if ( | ||
| ["TOKEN_EXPIRED", "INVALID_TOKEN"].includes( | ||
| errorData.error.errorClassName | ||
| ) | ||
| ) { | ||
| // 예: 로그아웃 처리 또는 토큰 재발급 로직 호출 | ||
| } |
There was a problem hiding this comment.
토큰 만료 처리 로직이 비어있습니다.
TOKEN_EXPIRED, INVALID_TOKEN 에러를 감지하지만 실제 처리 로직(로그아웃 또는 토큰 재발급)이 구현되지 않았습니다. 현재 상태에서는 인증 실패 시 사용자가 아무런 피드백 없이 작업이 실패하게 됩니다.
💡 최소한의 개선 제안
if (
["TOKEN_EXPIRED", "INVALID_TOKEN"].includes(
errorData.error.errorClassName
)
) {
- // 예: 로그아웃 처리 또는 토큰 재발급 로직 호출
+ // 클라이언트 사이드에서만 리다이렉트 처리
+ if (typeof window !== "undefined") {
+ window.location.href = "/login";
+ }
}PR 설명에서 API 명세가 아직 준비되지 않았다고 언급되어 있으니, 추후 토큰 재발급 로직 구현 시 이 부분을 함께 처리하시면 됩니다.
이 부분에 대한 구현을 도와드릴까요? 또는 TODO 이슈를 생성해드릴까요?
📝 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.
| if ( | |
| ["TOKEN_EXPIRED", "INVALID_TOKEN"].includes( | |
| errorData.error.errorClassName | |
| ) | |
| ) { | |
| // 예: 로그아웃 처리 또는 토큰 재발급 로직 호출 | |
| } | |
| if ( | |
| ["TOKEN_EXPIRED", "INVALID_TOKEN"].includes( | |
| errorData.error.errorClassName | |
| ) | |
| ) { | |
| // 클라이언트 사이드에서만 리다이렉트 처리 | |
| if (typeof window !== "undefined") { | |
| window.location.href = "/login"; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/fetcher.ts` around lines 73 - 79, The if-block checking
errorData.error.errorClassName for "TOKEN_EXPIRED"/"INVALID_TOKEN" is empty;
implement token-expiry handling by invoking your auth flow: call a refresh
function (e.g., refreshAuthToken()) and, if it succeeds, retry the original
request, otherwise call logoutUser() (or emit an auth failure event via
authEventEmitter/onAuthFailure) and throw or return a clear AuthError so callers
can show UI feedback; place this logic inside the existing if that references
errorData.error.errorClassName and add a TODO fallback if refresh endpoint is
not yet available.
🛠️ 변경 사항
작업된 내용이 좀 많습니다 허허;;
(community-category) 라우트 그룹 신설
검색 페이지
sort-group.tsx 시간순 / 인기순 정렬버튼 추가
use-query-parmas.tsx 쿼리파람 커스텀훅 추가
formatRelativeDate(dateString: string): string 시간 계산 유틸 추가
여기에 설명을 작성해주세요.
📸 스크린샷 (선택)
검색 기록이 없을 때
검색 기록이 있을 때
✅ 체크리스트
🔗 연결된 이슈
해당 api 명세서가 없어 일단 해당작업내용만 먼저 올릴게요
Summary by CodeRabbit
릴리스 노트
새 기능
개선사항