[FEAT] 알바찾기 페이지 UI & API 구현#33
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughNaver Maps 기반 채용 공고 지도 기능이 신규 구현되며, 공고 조회/상세/지원 페이지와 React Query 무한 스크롤, framer-motion 드래그 시트가 포함됨. 동시에 Kakao OAuth가 ChangesKakao OAuth 다중 반환값 통합
채용 공고 기능 전체 구현
의존성 추가 및 컴포넌트 정리
🎯 추정 검토 난이도🎯 4 (Complex) | ⏱️ ~50분 이유: 다중 페이지 지형/지도 통합, 복잡한 상태 관리(위치 추적·드래그·무한 스크롤), OAuth 반환값 확장의 이중 호출부 업데이트, 컴포넌트 재배치로 인한 이주 검증 필요. 단, 로직 밀도는 중간 수준이고 대부분 신규 파일이라 회귀 범위는 제한적. 🔍 시니어 리뷰 - 핵심 이슈
|
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 12.90% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | PR 제목이 주요 변경 사항인 알바찾기 페이지 UI & API 구현을 명확하게 반영하고 있습니다. |
| Description check | ✅ Passed | PR 설명이 템플릿의 필수 섹션(ID, 변경 내용, 구현 사항)을 모두 포함하고 있으며 구체적인 변경 내용을 제시하고 있습니다. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
feat/ALT-191
Tip
💬 Introducing Slack Agent: The best way for teams to turn conversations into code.
Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
- Generate code and open pull requests
- Plan features and break down work
- Investigate incidents and troubleshoot customer tickets together
- Automate recurring tasks and respond to alerts with triggers
- Summarize progress and report instantly
Built for teams:
- Shared memory across your entire org—no repeating context
- Per-thread sandboxes to safely plan and execute work
- Governance built-in—scoped access, auditability, and budget controls
One agent for your entire SDLC. Right inside Slack.
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 @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/pages/user/job-lookup-map/index.tsx (1)
62-169: 🏗️ Heavy lift페이지 레이어 책임이 커져서 유지보수 리스크가 높습니다.
지도 생성/위치추적/시트 물리/페이징 트리거 로직을 feature 훅(예:
useJobLookupMapController)으로 분리하면 페이지는 조합만 담당하고 회귀 범위를 크게 줄일 수 있습니다.As per coding guidelines
src/pages/**: "페이지 컴포넌트가 비즈니스 로직 없이 조합(Composition)만 하는지".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/user/job-lookup-map/index.tsx` around lines 62 - 169, The page component contains map creation, geolocation watch, sheet physics, and infinite-scroll observer logic that should be extracted into a feature hook; implement a new hook named useJobLookupMapController that encapsulates the logic currently inside the useEffect blocks, useLayoutEffect, the IntersectionObserver setup, lastPositionRef/mapInstanceRef/sheetRef/loadMoreRef state, and the snapTo animation so the page becomes purely composition. Move responsibilities: initialize Naver map and FALLBACK_LAT/LNG center, start/clear navigator.geolocation.watchPosition, manage map.setCenter calls (used by handleMyLocationClick), create and cleanup ResizeObserver for sheet resizing (updateBounds and setMaxTranslateY/y updates), setup IntersectionObserver to drive fetchNextPage/isFetchingNextPage/hasNextPage, and return refs (mapContainerRef, sheetRef, loadMoreRef), state (maxTranslateY), and handlers (snapTo, handleMyLocationClick, handleListClick) so the page component imports useJobLookupMapController and only wires props and UI.src/pages/user/job-lookup-map-detail/index.tsx (1)
13-59: ⚡ Quick win페이지 레이어에 비즈니스 포맷팅 로직이 과도하게 들어와 있습니다.
Line 13-59의 요일 파싱/근무시간 계산 로직은
features또는 전용 selector/helper로 분리하고 페이지는 조합에 집중하는 편이 유지보수와 재사용에 안전합니다.As per coding guidelines
src/pages/**: "페이지 컴포넌트가 비즈니스 로직 없이 조합(Composition)만 하는지".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/user/job-lookup-map-detail/index.tsx` around lines 13 - 59, The page component JobLookupMapDetailPage currently contains business logic functions parseWorkDayLabels and formatDurationHint and computes schedule-derived values (workDaysLine, selectedDays, timeRange, durationHint); extract these into a feature/helper (e.g., a new module with exported helpers like parseWorkDayLabels, formatDurationHint, and a selector function formatScheduleForDisplay that uses formatWorkDaysForDisplay) and import them into the page so JobLookupMapDetailPage only calls the helpers to get selectedDays, timeRange and durationHint; update usages of schedule.startTime/schedule.endTime to route through the new helper API and remove the inline implementations from the page.src/shared/constants/routes.ts (1)
16-17: 💤 Low value동적 라우트 네이밍 일관성 검토를 제안합니다.
기존 동적 세그먼트를 포함한 라우트 상수들(
WORKSPACE_MEMBERS_PATTERN,WORKSPACE_DETAIL_PATTERN)은_PATTERN접미사를 사용하는데, 신규 추가된 라우트는 이를 따르지 않습니다. 일관된 네이밍 컨벤션을 유지하면 코드베이스 탐색과 유지보수가 더 용이합니다.📝 일관성을 위한 네이밍 제안
-JOB_LOOKUP_MAP_DETAIL: '/user/job-lookup-map-detail/:postingId', -JOB_LOOKUP_MAP_APPLY: '/user/job-lookup-map-apply/:postingId', +JOB_LOOKUP_MAP_DETAIL_PATTERN: '/user/job-lookup-map-detail/:postingId', +JOB_LOOKUP_MAP_APPLY_PATTERN: '/user/job-lookup-map-apply/:postingId',App.tsx에서도 해당 상수명 업데이트 필요합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/constants/routes.ts` around lines 16 - 17, Rename the new dynamic route constants to follow the existing `_PATTERN` naming convention so they match WORKSPACE_MEMBERS_PATTERN and WORKSPACE_DETAIL_PATTERN; specifically update JOB_LOOKUP_MAP_DETAIL and JOB_LOOKUP_MAP_APPLY to use the `_PATTERN` suffix (and update any usages), then adjust references in App.tsx to the new constant names to avoid breaking imports/usages and keep naming consistent across the codebase.src/app/App.tsx (1)
16-17: ⚡ Quick win신규 페이지에 lazy loading 적용을 권장합니다.
SignupPage는 lazy import를 사용하지만, 새로 추가된JobLookupMapApplyPage와JobLookupMapDetailPage는 eager import되어 초기 번들 크기가 증가합니다. 사용자 흐름상 이 페이지들은 지도 페이지 이후에 접근하므로 code splitting이 적합합니다.⚡ lazy import로 변경하는 제안
-import { JobLookupMapApplyPage } from '`@/pages/user/job-lookup-map-apply`' -import { JobLookupMapDetailPage } from '`@/pages/user/job-lookup-map-detail`' + +const JobLookupMapApplyPage = lazy(async () => { + const m = await import('`@/pages/user/job-lookup-map-apply`') + return { default: m.JobLookupMapApplyPage } +}) + +const JobLookupMapDetailPage = lazy(async () => { + const m = await import('`@/pages/user/job-lookup-map-detail`') + return { default: m.JobLookupMapDetailPage } +})Route 정의에 Suspense 추가:
<Route path={ROUTES.USER.JOB_LOOKUP_MAP_DETAIL} - element={<JobLookupMapDetailPage />} + element={ + <Suspense fallback={null}> + <JobLookupMapDetailPage /> + </Suspense> + } /> <Route path={ROUTES.USER.JOB_LOOKUP_MAP_APPLY} - element={<JobLookupMapApplyPage />} + element={ + <Suspense fallback={null}> + <JobLookupMapApplyPage /> + </Suspense> + } />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/App.tsx` around lines 16 - 17, 현재 eager로 가져오는 JobLookupMapApplyPage와 JobLookupMapDetailPage는 초기 번들에 포함되므로 React.lazy로 변경해 코드 스플리팅을 적용하세요: replace the direct imports of JobLookupMapApplyPage and JobLookupMapDetailPage with lazy imports using React.lazy(() => import('...')) and ensure the routes that render these components are wrapped in a React.Suspense with an appropriate fallback (similar to how SignupPage is already lazy-loaded); confirm the target modules export default components so lazy import works and add React/Suspense imports where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/features/job-lookup-map/api/posting.ts`:
- Around line 37-48: The function currently attempts to unwrap a
CommonApiResponse<PostingDetailResponse> by checking 'timestamp' and 'data' but
then force-casts to PostingDetailResponse even when body.data is empty/invalid,
causing unsafe casting; modify the unwrap logic that returns (body as
CommonApiResponse<PostingDetailResponse>).data so it first validates that
body.data contains the expected PostingDetailResponse shape (or required fields)
and, if not valid, either return null/undefined or throw a descriptive Error
instead of casting the wrapper to PostingDetailResponse; update the conditional
around body/data checks and the code paths that return the unwrapped value
(references: the body variable, CommonApiResponse<PostingDetailResponse>, and
PostingDetailResponse) so callers never receive an incorrectly cast object.
In `@src/features/job-lookup-map/common/Albabox.tsx`:
- Around line 34-37: The article element in the Albabox component uses only
onClick (onClick) so keyboard users cannot activate the card; replace the
clickable article with a semantic interactive element (e.g., button or Link) or
at minimum make the article focusable and keyboard-activatable by adding
tabIndex={0}, role="button", and a keyDown handler that calls the same handler
used by onClick when Enter or Space is pressed; update the component where
onClick is declared/used (Albabox) to reuse the existing click handler for
keyboard events to ensure identical behavior and accessibility.
In `@src/pages/user/job-lookup-map-apply/index.tsx`:
- Around line 273-279: The submit button currently silently returns when no
schedule exists (selectedScheduleId ?? data.schedules[0]?.id) so users see an
unresponsive button; update the button logic in the component that uses
isSubmitting, selectedScheduleId, data.schedules and submitApply to instead
disable the button when data.schedules.length === 0 (add that condition to the
disabled prop) and render a visible helper/alert text near the button explaining
"No schedules available" (or similar) so users understand why submission is
disabled; ensure the onClick no longer needs to guard for missing schedule since
the button will be disabled in that case.
In `@src/pages/user/job-lookup-map-detail/index.tsx`:
- Around line 178-183: The "더보기" button rendered in the JSX (the <button> with
text "더보기" and className "mt-3 h-12 w-full rounded-2xl border border-line-2
typography-body02-semibold text-text-70") currently has no behavior; either wire
it to a real handler (e.g., add an onClick prop like onClick={handleMoreClick}
and implement/export handleMoreClick in this component to perform the intended
action) or make it non-interactive to avoid appearing broken (add disabled and
aria-disabled attributes and update styling, or conditionally hide it via a
boolean flag and render null when not ready). Ensure the chosen approach touches
this exact button element so the UI no longer shows an inert clickable control.
In `@src/pages/user/job-lookup-map/index.tsx`:
- Around line 144-148: The updateBounds function currently forces the sheet
closed on every resize by unconditionally calling y.set(nextMax); change
updateBounds to only adjust maxTranslateY (call setMaxTranslateY(nextMax)) and
then clamp the current y value to the new allowed range instead of overwriting
it — use the existing y value and only set y when it exceeds the new bounds
(e.g., clamp y to [0, nextMax] or set to nextMax only if y.get() > nextMax) so
the sheet stays in its current open position unless out-of-range; reference
updateBounds, nextMax, setMaxTranslateY, y and SHEET_PEEK_HEIGHT when making the
change.
In `@src/shared/ui/common/SearchBar.tsx`:
- Around line 12-33: The SearchBar component can render an unnamed <input> if
callers don't pass aria-label/aria-labelledby; update SearchBar to ensure an
accessible name by detecting if props includes aria-label or aria-labelledby
and, if absent, provide a sensible fallback (e.g., use the placeholder or a
default string like "Search") when rendering the input; refer to the SearchBar
function, the input element that spreads {...props}, and the placeholder prop to
implement this fallback so screen readers always receive an accessible name.
---
Nitpick comments:
In `@src/app/App.tsx`:
- Around line 16-17: 현재 eager로 가져오는 JobLookupMapApplyPage와
JobLookupMapDetailPage는 초기 번들에 포함되므로 React.lazy로 변경해 코드 스플리팅을 적용하세요: replace the
direct imports of JobLookupMapApplyPage and JobLookupMapDetailPage with lazy
imports using React.lazy(() => import('...')) and ensure the routes that render
these components are wrapped in a React.Suspense with an appropriate fallback
(similar to how SignupPage is already lazy-loaded); confirm the target modules
export default components so lazy import works and add React/Suspense imports
where needed.
In `@src/pages/user/job-lookup-map-detail/index.tsx`:
- Around line 13-59: The page component JobLookupMapDetailPage currently
contains business logic functions parseWorkDayLabels and formatDurationHint and
computes schedule-derived values (workDaysLine, selectedDays, timeRange,
durationHint); extract these into a feature/helper (e.g., a new module with
exported helpers like parseWorkDayLabels, formatDurationHint, and a selector
function formatScheduleForDisplay that uses formatWorkDaysForDisplay) and import
them into the page so JobLookupMapDetailPage only calls the helpers to get
selectedDays, timeRange and durationHint; update usages of
schedule.startTime/schedule.endTime to route through the new helper API and
remove the inline implementations from the page.
In `@src/pages/user/job-lookup-map/index.tsx`:
- Around line 62-169: The page component contains map creation, geolocation
watch, sheet physics, and infinite-scroll observer logic that should be
extracted into a feature hook; implement a new hook named
useJobLookupMapController that encapsulates the logic currently inside the
useEffect blocks, useLayoutEffect, the IntersectionObserver setup,
lastPositionRef/mapInstanceRef/sheetRef/loadMoreRef state, and the snapTo
animation so the page becomes purely composition. Move responsibilities:
initialize Naver map and FALLBACK_LAT/LNG center, start/clear
navigator.geolocation.watchPosition, manage map.setCenter calls (used by
handleMyLocationClick), create and cleanup ResizeObserver for sheet resizing
(updateBounds and setMaxTranslateY/y updates), setup IntersectionObserver to
drive fetchNextPage/isFetchingNextPage/hasNextPage, and return refs
(mapContainerRef, sheetRef, loadMoreRef), state (maxTranslateY), and handlers
(snapTo, handleMyLocationClick, handleListClick) so the page component imports
useJobLookupMapController and only wires props and UI.
In `@src/shared/constants/routes.ts`:
- Around line 16-17: Rename the new dynamic route constants to follow the
existing `_PATTERN` naming convention so they match WORKSPACE_MEMBERS_PATTERN
and WORKSPACE_DETAIL_PATTERN; specifically update JOB_LOOKUP_MAP_DETAIL and
JOB_LOOKUP_MAP_APPLY to use the `_PATTERN` suffix (and update any usages), then
adjust references in App.tsx to the new constant names to avoid breaking
imports/usages and keep naming consistent across the codebase.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c53ac4df-ef24-468e-b767-8ac9cba1e1b0
⛔ Files ignored due to path filters (8)
package-lock.jsonis excluded by!**/package-lock.jsonsrc/assets/icons/job-lookup-map/Bookmark.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/Calendar.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/Chevrondown.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/Clock.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/List.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/Mappin.svgis excluded by!**/*.svgsrc/assets/icons/job-lookup-map/Thumbsup.svgis excluded by!**/*.svg
📒 Files selected for processing (29)
index.htmlpackage.jsonsrc/app/App.tsxsrc/features/auth/ui/KakaoLoginButton.tsxsrc/features/job-lookup-map/api/posting.tssrc/features/job-lookup-map/common/AlbaFindCategoryBar.tsxsrc/features/job-lookup-map/common/AlbaFindList.tsxsrc/features/job-lookup-map/common/Albabox.tsxsrc/features/job-lookup-map/common/MapFloatingActions.tsxsrc/features/job-lookup-map/hooks/useApplyPosting.tssrc/features/job-lookup-map/hooks/usePosting.tssrc/features/job-lookup-map/hooks/usePostingDetail.tssrc/features/job-lookup-map/lib/postingToAlbaboxProps.tssrc/features/job-lookup-map/types/posting.tssrc/features/social/index.tssrc/features/social/ui/AlbaFindDrawer.tsxsrc/features/social/ui/DrawerHandleBar.tsxsrc/features/social/ui/DrawerPeekStrip.tsxsrc/features/social/ui/drawer.tsxsrc/pages/signup/hooks/useSignupForm.tssrc/pages/user/job-lookup-map-apply/index.tsxsrc/pages/user/job-lookup-map-detail/index.tsxsrc/pages/user/job-lookup-map/index.tsxsrc/shared/constants/routes.tssrc/shared/lib/socialLogin.tssrc/shared/ui/common/SearchBar.tsxsrc/shared/ui/manager/OngoingPostingCard.tsxsrc/shared/ui/manager/alba-find/AlbaFindCategoryBar.tsxsrc/shared/ui/manager/alba-find/Albabox.tsx
💤 Files with no reviewable changes (7)
- src/features/social/ui/DrawerHandleBar.tsx
- src/features/social/ui/AlbaFindDrawer.tsx
- src/shared/ui/manager/alba-find/AlbaFindCategoryBar.tsx
- src/features/social/ui/drawer.tsx
- src/features/social/index.ts
- src/features/social/ui/DrawerPeekStrip.tsx
- src/shared/ui/manager/alba-find/Albabox.tsx
| if ( | ||
| body && | ||
| typeof body === 'object' && | ||
| 'timestamp' in body && | ||
| 'data' in body && | ||
| body.data != null && | ||
| typeof body.data === 'object' | ||
| ) { | ||
| return (body as CommonApiResponse<PostingDetailResponse>).data | ||
| } | ||
| return body as PostingDetailResponse | ||
| } |
There was a problem hiding this comment.
상세 응답 언래핑 실패 시 잘못된 캐스팅이 발생합니다.
CommonApiResponse 형태인데 data가 비어있거나 비정상이면 현재 로직은 wrapper 전체를 PostingDetailResponse로 캐스팅해 반환합니다(Line 47). 이 경우 호출부에서 필드 접근 시 런타임 오류로 이어질 수 있습니다.
수정 예시
const body = response.data
- if (
- body &&
- typeof body === 'object' &&
- 'timestamp' in body &&
- 'data' in body &&
- body.data != null &&
- typeof body.data === 'object'
- ) {
- return (body as CommonApiResponse<PostingDetailResponse>).data
- }
- return body as PostingDetailResponse
+ if (body && typeof body === 'object' && 'timestamp' in body && 'data' in body) {
+ const wrapped = body as CommonApiResponse<PostingDetailResponse | null>
+ if (!wrapped.data) {
+ throw new Error('Invalid posting detail response: data is null')
+ }
+ return wrapped.data
+ }
+ return body as PostingDetailResponse📝 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 ( | |
| body && | |
| typeof body === 'object' && | |
| 'timestamp' in body && | |
| 'data' in body && | |
| body.data != null && | |
| typeof body.data === 'object' | |
| ) { | |
| return (body as CommonApiResponse<PostingDetailResponse>).data | |
| } | |
| return body as PostingDetailResponse | |
| } | |
| const body = response.data | |
| if (body && typeof body === 'object' && 'timestamp' in body && 'data' in body) { | |
| const wrapped = body as CommonApiResponse<PostingDetailResponse | null> | |
| if (!wrapped.data) { | |
| throw new Error('Invalid posting detail response: data is null') | |
| } | |
| return wrapped.data | |
| } | |
| return body as PostingDetailResponse | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/job-lookup-map/api/posting.ts` around lines 37 - 48, The
function currently attempts to unwrap a CommonApiResponse<PostingDetailResponse>
by checking 'timestamp' and 'data' but then force-casts to PostingDetailResponse
even when body.data is empty/invalid, causing unsafe casting; modify the unwrap
logic that returns (body as CommonApiResponse<PostingDetailResponse>).data so it
first validates that body.data contains the expected PostingDetailResponse shape
(or required fields) and, if not valid, either return null/undefined or throw a
descriptive Error instead of casting the wrapper to PostingDetailResponse;
update the conditional around body/data checks and the code paths that return
the unwrapped value (references: the body variable,
CommonApiResponse<PostingDetailResponse>, and PostingDetailResponse) so callers
never receive an incorrectly cast object.
| <article | ||
| className="border-b border-line-1 py-5 last:border-b-0 cursor-pointer" | ||
| onClick={onClick} | ||
| > |
There was a problem hiding this comment.
카드 클릭 진입이 키보드로 불가능합니다.
Line 34-37에서 상세 진입이 onClick만으로 연결되어 있어 키보드 사용자(탭/엔터)로는 공고 상세 이동을 수행할 수 없습니다. 카드 진입은 button/Link 기반으로 바꾸거나, 최소한 포커스/키보드 활성화(Enter/Space)를 보장해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/features/job-lookup-map/common/Albabox.tsx` around lines 34 - 37, The
article element in the Albabox component uses only onClick (onClick) so keyboard
users cannot activate the card; replace the clickable article with a semantic
interactive element (e.g., button or Link) or at minimum make the article
focusable and keyboard-activatable by adding tabIndex={0}, role="button", and a
keyDown handler that calls the same handler used by onClick when Enter or Space
is pressed; update the component where onClick is declared/used (Albabox) to
reuse the existing click handler for keyboard events to ensure identical
behavior and accessibility.
| <button | ||
| type="button" | ||
| disabled={isSubmitting} | ||
| onClick={() => { | ||
| const scheduleId = selectedScheduleId ?? data.schedules[0]?.id | ||
| if (!scheduleId) return | ||
| submitApply({ |
There was a problem hiding this comment.
스케줄이 없을 때 제출 버튼이 무반응입니다.
Line 277-279에서 스케줄이 없으면 조용히 return해서, 사용자는 버튼이 먹통처럼 보입니다. data.schedules.length === 0일 때 제출 버튼을 비활성화하고 안내 문구를 같이 노출해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/user/job-lookup-map-apply/index.tsx` around lines 273 - 279, The
submit button currently silently returns when no schedule exists
(selectedScheduleId ?? data.schedules[0]?.id) so users see an unresponsive
button; update the button logic in the component that uses isSubmitting,
selectedScheduleId, data.schedules and submitApply to instead disable the button
when data.schedules.length === 0 (add that condition to the disabled prop) and
render a visible helper/alert text near the button explaining "No schedules
available" (or similar) so users understand why submission is disabled; ensure
the onClick no longer needs to guard for missing schedule since the button will
be disabled in that case.
| <button | ||
| type="button" | ||
| className="mt-3 h-12 w-full rounded-2xl border border-line-2 typography-body02-semibold text-text-70" | ||
| > | ||
| 더보기 | ||
| </button> |
There was a problem hiding this comment.
동작 없는 버튼이 노출되어 있습니다.
Line 178-183의 더보기 버튼은 클릭 시 아무 동작이 없어 사용자 입장에서 고장난 UI로 보입니다. 실제 동작을 연결하거나, 준비 중이라면 비활성/숨김 처리로 오동작 인상을 막아주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/user/job-lookup-map-detail/index.tsx` around lines 178 - 183, The
"더보기" button rendered in the JSX (the <button> with text "더보기" and className
"mt-3 h-12 w-full rounded-2xl border border-line-2 typography-body02-semibold
text-text-70") currently has no behavior; either wire it to a real handler
(e.g., add an onClick prop like onClick={handleMoreClick} and implement/export
handleMoreClick in this component to perform the intended action) or make it
non-interactive to avoid appearing broken (add disabled and aria-disabled
attributes and update styling, or conditionally hide it via a boolean flag and
render null when not ready). Ensure the chosen approach touches this exact
button element so the UI no longer shows an inert clickable control.
| const updateBounds = () => { | ||
| const nextMax = Math.max(0, sheet.offsetHeight - SHEET_PEEK_HEIGHT) | ||
| setMaxTranslateY(nextMax) | ||
| y.set(nextMax) | ||
| } |
There was a problem hiding this comment.
시트 위치가 리사이즈 때마다 강제로 접히는 버그가 있습니다.
Line 147에서 y.set(nextMax)를 매번 실행해, 사용자가 펼쳐둔 시트가 콘텐츠/뷰포트 변경 시 갑자기 내려갑니다. 현재 y를 유지하고 새 범위를 벗어날 때만 clamp 하도록 바꿔주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/user/job-lookup-map/index.tsx` around lines 144 - 148, The
updateBounds function currently forces the sheet closed on every resize by
unconditionally calling y.set(nextMax); change updateBounds to only adjust
maxTranslateY (call setMaxTranslateY(nextMax)) and then clamp the current y
value to the new allowed range instead of overwriting it — use the existing y
value and only set y when it exceeds the new bounds (e.g., clamp y to [0,
nextMax] or set to nextMax only if y.get() > nextMax) so the sheet stays in its
current open position unless out-of-range; reference updateBounds, nextMax,
setMaxTranslateY, y and SHEET_PEEK_HEIGHT when making the change.
| export function SearchBar({ | ||
| className = '', | ||
| wrapperClassName = '', | ||
| placeholder = '', | ||
| ...props | ||
| }: SearchBarProps) { | ||
| return ( | ||
| <div | ||
| className={`relative flex h-12 w-full items-center rounded-[16px] border border-line-2 bg-white shadow-[0px_2px_8px_rgba(0,0,0,0.08)] ${wrapperClassName}`} | ||
| > | ||
| <img | ||
| src={searchIcon} | ||
| alt="" | ||
| className="pointer-events-none absolute left-4 h-[22px] w-[22px] shrink-0" | ||
| aria-hidden | ||
| /> | ||
| <input | ||
| type="search" | ||
| placeholder={placeholder} | ||
| className={`h-full w-full rounded-full bg-transparent py-0 pl-11 pr-4 typography-body03-regular text-text-100 placeholder:text-text-50 outline-none ${className}`} | ||
| {...props} | ||
| /> |
There was a problem hiding this comment.
검색 입력의 접근 가능한 이름을 보장해주세요.
현재 구현은 호출부가 aria-label/aria-labelledby를 넘기지 않으면 이름 없는 <input>이 렌더링될 수 있어 스크린리더 사용자가 필드 목적을 알기 어렵습니다. shared 컴포넌트에서 최소 하나의 접근성 이름 소스를 강제하는 게 안전합니다.
수정 예시
export type SearchBarProps = Omit<
InputHTMLAttributes<HTMLInputElement>,
'type' | 'className'
> & {
className?: string
wrapperClassName?: string
+ ariaLabel?: string
}
export function SearchBar({
className = '',
wrapperClassName = '',
placeholder = '',
+ ariaLabel,
...props
}: SearchBarProps) {
return (
@@
<input
type="search"
placeholder={placeholder}
+ aria-label={ariaLabel ?? placeholder || '검색'}
className={`h-full w-full rounded-full bg-transparent py-0 pl-11 pr-4 typography-body03-regular text-text-100 placeholder:text-text-50 outline-none ${className}`}
{...props}
/>As per coding guidelines, "**/*.tsx에서는 a11y는 서비스를 막는 수준(의미 있는 버튼/폼/이미지)만 지적" 규칙에 따라 폼 입력의 접근 가능한 이름 누락 가능성을 우선 지적했습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/shared/ui/common/SearchBar.tsx` around lines 12 - 33, The SearchBar
component can render an unnamed <input> if callers don't pass
aria-label/aria-labelledby; update SearchBar to ensure an accessible name by
detecting if props includes aria-label or aria-labelledby and, if absent,
provide a sensible fallback (e.g., use the placeholder or a default string like
"Search") when rendering the input; refer to the SearchBar function, the input
element that spreads {...props}, and the placeholder prop to implement this
fallback so screen readers always receive an accessible name.
ID
변경 내용
구현 사항
구현 시연 (필요 시)
참고 사항 (필요 시)
(예: 확인 사항, 참조 링크 등)
Summary by CodeRabbit
릴리스 노트