[Feat/404-48] ✨ feat: nofound 페이지 구현 & wipCard 추가 (준비중 안내)#100
[Feat/404-48] ✨ feat: nofound 페이지 구현 & wipCard 추가 (준비중 안내)#100swallowedB merged 4 commits intodevfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughWIP 기능 알림 훅과 오버레이 컴포넌트, Space 배경 스타일과 컴포넌트, 글로벌 404 페이지(NotFound)를 추가했습니다. SiteHeader와 DockMenu에 WIP 알림 트리거가 통합되고 DockMenuItem에 선택적 onClick prop이 도입되었습니다. Changes
Sequence DiagramsequenceDiagram
actor User
participant DockMenu
participant useWipFeatureNotice
participant WipFeatureOverlay
participant WipFeatureCard
User->>DockMenu: PHOTOBOOTH 클릭
DockMenu->>useWipFeatureNotice: openNotice()
useWipFeatureNotice->>useWipFeatureNotice: isOpen = true (auto-close 타이머 설정)
useWipFeatureNotice-->>DockMenu: 상태 변경 반영
DockMenu->>WipFeatureOverlay: isOpen={true}
WipFeatureOverlay->>WipFeatureCard: 카드 렌더링 (애니메이션)
User->>WipFeatureCard: 닫기 버튼 클릭
WipFeatureCard->>WipFeatureOverlay: onClose()
WipFeatureOverlay->>useWipFeatureNotice: closeNotice()
useWipFeatureNotice->>useWipFeatureNotice: isOpen = false
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (4)
✏️ Tip: You can disable this entire section by setting 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
🤖 Fix all issues with AI agents
In `@src/app/`(layout)/(shell)/_components/dock/DockMenu.tsx:
- Around line 90-93: Replace the fragile magic-string check in handleItemClick
by introducing an explicit isWip flag on the dock item config and using that
flag for behavior: add isWip?: boolean to DOCK_ITEMS entries (e.g., the
PHOTOBOOTH button), update the type for dock items to include isWip, and change
the handleItemClick logic to use item.isWip (combined with type/button check) to
decide whether to call openNotice instead of comparing item.label to
"PHOTOBOOTH".
In `@src/app/`(layout)/(shell)/_components/home/HeroRightMedia.tsx:
- Around line 8-15: Update the Image in the HeroRightMedia component: replace
the non-descriptive alt "임시" with a meaningful description of the illustration
(e.g., describe the elephant illustration’s purpose) or set alt="" if the
graphic is purely decorative; verify whether the intended asset is
"/error/elephant.svg" or "/error/wip-card.svg" and switch the src to the correct
file if needed; and remove the explicit priority={false} prop since it’s the
Next.js default.
In `@src/app/`(layout)/(shell)/_components/layout/SiteHeader.tsx:
- Around line 68-95: Replace the three repeated <li> button blocks in SiteHeader
by extracting labels into a constant array (e.g. WIP_NAV_ITEMS =
["Resume","Guestbook","Lab"]) and render them with .map(), returning an <li
key={label} className="hover:text-foreground"> containing the <button
type="button" onClick={handleClick} className="text-sm
cursor-pointer">{label}</button>; ensure each mapped item uses a stable key
(label is fine here) and preserve the existing className and onClick behavior.
- Around line 23-25: The local function handleClick simply calls openNotice()
and is unnecessary; remove the handleClick declaration and update the element
using it to pass the handler directly as onClick={openNotice} (replace any
references to handleClick with openNotice) so the component uses openNotice as
the click handler without the extra wrapper.
In `@src/app/not-found.tsx`:
- Around line 5-47: The NotFound component uses a missing CSS utility class
shimmer-text; add a definition for .shimmer-text in your global utilities or
components styles (e.g., under your utilities/styles module) that implements the
shimmer effect (background gradient, background-clip:text or color overlay,
keyframes for sliding shimmer, and animation properties) so the two <p> elements
in NotFound render with the intended animated shimmer; ensure you include the
keyframes under the same stylesheet and export/import the stylesheet so it is
applied globally.
In `@src/components/common/SpaceBackground.tsx`:
- Line 1: This component appears to be pure presentational (no hooks, state, or
event handlers) so remove the "use client" directive at the top of
SpaceBackground.tsx to allow it to be a server component and reduce client
bundle size; verify the SpaceBackground component (and any helpers it imports)
do not use React hooks, browser-only APIs, or event handlers, and if none are
present simply delete the `"use client"` line and keep the existing JSX/exports.
In `@src/components/common/WipFeatureCard.tsx`:
- Around line 14-17: The div in WipFeatureCard (the JSX element with
className={clsx("relative w-[min(440px,90vw)]", className)} and onClick={(e) =>
e.stopPropagation()}) currently has an onClick handler but no accessibility
role; update that element to include role="presentation" (and optionally
tabIndex={-1} if needed) to indicate it is non-interactive and silence
keyboard/accessibility warnings while preserving the stopPropagation behavior.
- Line 28: The class string in WipFeatureCard.tsx contains an undefined Tailwind
class "rotate-80", so the rotation won't work; replace "rotate-80" with a valid
Tailwind rotate utility (e.g., "rotate-90" or "rotate-45") in the className
where "transition rotate-80 " appears (inside the WipFeatureCard component) or
add a custom rotate value to tailwind.config.ts if a non-standard angle is
required; ensure the updated className uses a supported rotate-* utility so the
rotation animates as intended.
In `@src/components/common/WipFeatureOverlay.tsx`:
- Around line 12-15: The component WipFeatureOverlay returns null on server via
a typeof document === "undefined" check which causes hydration mismatch; replace
that check by tracking client mount with useState/useEffect (e.g., const
[mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []))
and only render the Portal when mounted is true (and isOpen as before), removing
the direct typeof document guard so server renders the same markup as the client
until the client mount toggles rendering.
- Around line 20-23: The backdrop div in WipFeatureOverlay (the element with
className "fixed inset-0 z-60 flex items-center justify-center" and
onClick={onClose}) lacks keyboard handling and ARIA attributes; add an onKeyDown
handler that listens for Escape and calls onClose, make the element focusable
with tabIndex={0}, and add appropriate ARIA attributes (e.g., role="button" or
role="presentation" depending on surrounding markup, and aria-label like "Close
overlay" or use role="dialog" + aria-modal="true" on the modal container if this
is the dialog root) so keyboard users can close the overlay. Ensure the onClose
prop is reused and that focus is moved into/out of the overlay as appropriate
(e.g., set initial focus on mount or return focus on unmount) to complete
accessible behavior.
In `@src/hooks/useWipFeatureNotice.ts`:
- Around line 15-23: The openNotice function can leave dangling timers; store
the timeout ID in a ref (e.g., noticeTimerRef) and before creating a new
window.setTimeout clear any existing timer with
clearTimeout(noticeTimerRef.current), assign the new ID to
noticeTimerRef.current, and ensure you add a useEffect cleanup that clears
noticeTimerRef.current on unmount to prevent multiple simultaneous timers and
setState calls after unmount (affecting openNotice, setIsOpen, and autoCloseMs).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (5)
public/error/404.svgis excluded by!**/*.svgpublic/error/double-arrow-icon.svgis excluded by!**/*.svgpublic/error/elephant.svgis excluded by!**/*.svgpublic/error/oops.svgis excluded by!**/*.svgpublic/error/wip-card.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
src/app/(layout)/(shell)/_components/dock/DockMenu.tsxsrc/app/(layout)/(shell)/_components/dock/DockMenuItem.tsxsrc/app/(layout)/(shell)/_components/dock/dock.types.tssrc/app/(layout)/(shell)/_components/home/HeroRightMedia.tsxsrc/app/(layout)/(shell)/_components/layout/SiteHeader.tsxsrc/app/globals.csssrc/app/not-found.tsxsrc/components/common/SpaceBackground.tsxsrc/components/common/WipFeatureCard.tsxsrc/components/common/WipFeatureOverlay.tsxsrc/hooks/useWipFeatureNotice.tssrc/styles/components/background.css
🧰 Additional context used
🧬 Code graph analysis (5)
src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx (3)
src/app/(layout)/(shell)/_components/dock/dock.types.ts (1)
DockMenuItemProps(45-50)src/app/(layout)/(shell)/_components/dock/_hooks/useDockMenu.ts (3)
e(21-36)dockState(7-76)setDockState(51-53)src/app/(layout)/(shell)/_components/dock/_hooks/useDockInteraction.ts (1)
entry(35-39)
src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx (2)
src/hooks/useWipFeatureNotice.ts (1)
useWipFeatureNotice(10-34)src/components/common/WipFeatureOverlay.tsx (1)
WipFeatureOverlay(12-51)
src/components/common/WipFeatureOverlay.tsx (1)
src/components/common/WipFeatureCard.tsx (1)
WipFeatureCard(12-44)
src/app/(layout)/(shell)/_components/dock/DockMenu.tsx (1)
src/hooks/useWipFeatureNotice.ts (1)
useWipFeatureNotice(10-34)
src/app/not-found.tsx (1)
src/components/common/SpaceBackground.tsx (1)
SpaceBackground(9-28)
🪛 Biome (2.1.2)
src/components/common/WipFeatureCard.tsx
[error] 14-17: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 14-17: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
src/components/common/WipFeatureOverlay.tsx
[error] 20-23: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 20-23: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
src/app/globals.css
[error] 23-23: This @import is in the wrong position.
Any @import rules must precede all other valid at-rules and style rules in a stylesheet (ignoring @charset and @layer), or else the @import rule is invalid.
Consider moving import position.
(lint/correctness/noInvalidPositionAtImportRule)
🔇 Additional comments (10)
src/app/(layout)/(shell)/_components/dock/dock.types.ts (1)
45-50: LGTM!
DockMenuItemProps에onClick옵셔널 prop을 추가한 것은 적절합니다. 기존item.onClick과 별개로 외부에서 클릭 동작을 오버라이드할 수 있는 유연성을 제공합니다.src/app/globals.css (1)
22-23: LGTM! (정적 분석 경고 확인 필요)배경 스타일 import 추가는 적절합니다. Biome에서
@import위치 관련 경고가 발생했으나, 모든@import가 스타일 규칙(.heading-anchor) 이전에 위치하므로 CSS 명세상 문제가 없어 보입니다.만약 빌드나 스타일 적용에 문제가 발생한다면, 다른
@import들과 동일한 섹션(line 6-20 사이)으로 이동을 고려해 주세요.src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx (1)
178-179: LGTM!
WipFeatureOverlay가createPortal을 사용하여document.body에 렌더링되므로,<header>내부에 배치해도 문제없습니다. WIP 기능 통합이 잘 구현되었습니다.src/components/common/SpaceBackground.tsx (1)
9-27: LGTM!깔끔한 프레젠테이셔널 컴포넌트입니다.
clsx를 활용한 className 병합과 레이어 구조가 잘 구성되어 있습니다.src/app/(layout)/(shell)/_components/dock/DockMenu.tsx (1)
156-160: LGTM!
WipFeatureOverlay통합이SiteHeader.tsx와 일관된 패턴으로 잘 구현되었습니다.src/styles/components/background.css (1)
1-74: LGTM! 우주 배경 스타일이 잘 구성되어 있습니다.애니메이션과 그라디언트가 적절히 정의되어 있고, 별똥별 효과의 딜레이 분산도 자연스럽습니다.
translate3d를 사용하여 GPU 가속을 활용한 점도 좋습니다.src/app/(layout)/(shell)/_components/dock/DockMenuItem.tsx (2)
8-8: LGTM! onClick prop 추가가 적절합니다.외부에서 클릭 핸들러를 주입할 수 있게 하면서,
item.onClick으로의 폴백을 유지하여 기존 동작과 호환됩니다.
42-54: 버튼 타입에서의 onClick 처리가 잘 되어 있습니다.널 병합 연산자(
??)를 사용하여 외부onClick이 없을 때item.onClick으로 자연스럽게 폴백됩니다.src/components/common/WipFeatureCard.tsx (1)
12-43: 전반적으로 잘 구현된 WIP 카드 컴포넌트입니다.닫기 버튼에 적절한
aria-label이 있고,clsx를 사용한 클래스 병합과stopPropagation을 통한 모달 패턴 구현이 적절합니다.src/components/common/WipFeatureOverlay.tsx (1)
24-45: 애니메이션 로직 확인 완료Framer Motion의
AnimatePresence와motion.div를 사용한 슬라이드 애니메이션 구현이 적절합니다.keyprop이 있어 AnimatePresence가 exit 애니메이션을 올바르게 처리할 수 있습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| const handleItemClick = | ||
| item.type === "button" && item.label === "PHOTOBOOTH" | ||
| ? () => openNotice() | ||
| : undefined; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
매직 스트링 사용에 대한 우려
item.label === "PHOTOBOOTH" 문자열 비교는 라벨이 변경될 경우 조용히 실패할 수 있습니다. WIP 상태를 dock item 설정에 명시적으로 추가하는 것이 더 안전합니다.
♻️ 개선 제안
DOCK_ITEMS 설정에 isWip?: boolean 속성을 추가:
// dockItem.config.ts
{
type: "button",
label: "PHOTOBOOTH",
icon: "camera",
isWip: true, // WIP 상태 명시
onClick: () => {},
}// DockMenu.tsx
const handleItemClick =
- item.type === "button" && item.label === "PHOTOBOOTH"
+ item.type === "button" && item.isWip
? () => openNotice()
: undefined;🤖 Prompt for AI Agents
In `@src/app/`(layout)/(shell)/_components/dock/DockMenu.tsx around lines 90 - 93,
Replace the fragile magic-string check in handleItemClick by introducing an
explicit isWip flag on the dock item config and using that flag for behavior:
add isWip?: boolean to DOCK_ITEMS entries (e.g., the PHOTOBOOTH button), update
the type for dock items to include isWip, and change the handleItemClick logic
to use item.isWip (combined with type/button check) to decide whether to call
openNotice instead of comparing item.label to "PHOTOBOOTH".
| <Image | ||
| src="/error/elephant.svg" | ||
| alt="임시" | ||
| width={40} | ||
| height={120} | ||
| className="w-full h-auto select-none drop-shadow-[10px_5px_3px_rgba(0,0,0,0.10)]" | ||
| priority={false} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find public -name "*.svg" -o -name "elephant.svg" -o -name "wip-card.svg" 2>/dev/null | head -20Repository: swallowedB/b-log
Length of output: 332
🏁 Script executed:
cat -n src/app/\(layout\)/\(shell\)/_components/home/HeroRightMedia.tsxRepository: swallowedB/b-log
Length of output: 578
🏁 Script executed:
git log --oneline -5 -- src/app/\(layout\)/\(shell\)/_components/home/HeroRightMedia.tsx 2>/dev/null || echo "No git history available"Repository: swallowedB/b-log
Length of output: 89
🏁 Script executed:
git diff HEAD~1 HEAD -- public/ 2>/dev/null | grep -E "elephant|wip-card" || echo "No recent changes to public/ found"Repository: swallowedB/b-log
Length of output: 94
alt 텍스트 개선이 필요합니다
이미지 경로와 파일은 정상적으로 존재하는데, 몇 가지 지점이 있습니다:
-
alt 텍스트:
"임시"는 스크린 리더 사용자에게 의미 있는 정보를 제공하지 못합니다. 일러스트의 목적이나 내용을 설명하는 텍스트로 변경하거나, 순수 장식용이라면alt=""로 설정하세요. -
이미지 선택:
/error/elephant.svg와/error/wip-card.svg가 모두 존재하는데, 이 컴포넌트에서 elephant.svg를 의도적으로 사용하는 건지 확인이 필요합니다. -
priority={false}: Next.js Image의 기본값이므로 생략 가능합니다.
🤖 Prompt for AI Agents
In `@src/app/`(layout)/(shell)/_components/home/HeroRightMedia.tsx around lines 8
- 15, Update the Image in the HeroRightMedia component: replace the
non-descriptive alt "임시" with a meaningful description of the illustration
(e.g., describe the elephant illustration’s purpose) or set alt="" if the
graphic is purely decorative; verify whether the intended asset is
"/error/elephant.svg" or "/error/wip-card.svg" and switch the src to the correct
file if needed; and remove the explicit priority={false} prop since it’s the
Next.js default.
| export default function NotFound() { | ||
| return ( | ||
| <section className="relative flex flex-col items-center justify-center min-h-screen w-full overflow-hidden px-6"> | ||
| <SpaceBackground /> | ||
|
|
||
| <div className="flex flex-col items-center gap-5 h-full mb-10"> | ||
| <h1> | ||
| <Image | ||
| src="/error/404.svg" | ||
| alt="404" | ||
| width={300} | ||
| height={150} | ||
| priority | ||
| /> | ||
| </h1> | ||
|
|
||
| <Image src="/error/oops.svg" alt="oops" width={220} height={80} /> | ||
|
|
||
| <Image | ||
| className="w-10 aspect-auto" | ||
| src="/error/double-arrow-icon.svg" | ||
| alt="" | ||
| width={40} | ||
| height={40} | ||
| /> | ||
| </div> | ||
|
|
||
| <p className="text-center font-sans text-white/75 shimmer-text font-medium leading-relaxed max-w-[420px]"> | ||
| 보아뱀이 잘못된 페이지를 삼켜버렸어요. | ||
| </p> | ||
| <p className="text-center font-sans text-sm text-white/55 shimmer-text font-medium leading-relaxed max-w-[420px]"> | ||
| 찾으려던 글은 아직 소화되지 않았거나, 존재하지 않는 페이지예요. | ||
| </p> | ||
|
|
||
| <Link | ||
| href="/" | ||
| className="mt-8 inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/5 px-5 py-2 text-sm font-medium text-white/90 backdrop-blur-md transition hover:bg-white/15 hover:border-white/40" | ||
| > | ||
| <span>홈으로 돌아가기</span> | ||
| </Link> | ||
| </section> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# 프로젝트 구조 확인
git ls-files | head -20Repository: swallowedB/b-log
Length of output: 522
🏁 Script executed:
# shimmer-text 클래스 검색
rg -n "shimmer-text" --type css --type scssRepository: swallowedB/b-log
Length of output: 88
🏁 Script executed:
# globals 또는 global 스타일 파일 찾기
fd -e css -e scss | grep -i "global\|style"Repository: swallowedB/b-log
Length of output: 333
🏁 Script executed:
# app 디렉토리 구조 확인
fd -t f . src/app -type f | head -30Repository: swallowedB/b-log
Length of output: 230
🏁 Script executed:
# shimmer-text 클래스를 모든 CSS 파일에서 검색
rg "shimmer-text" src/ --type cssRepository: swallowedB/b-log
Length of output: 42
🏁 Script executed:
# not-found.tsx 파일 내용 확인
cat -n src/app/not-found.tsxRepository: swallowedB/b-log
Length of output: 1878
🏁 Script executed:
# 루트 레이아웃 확인
cat -n src/app/layout.tsx | head -50Repository: swallowedB/b-log
Length of output: 1614
🏁 Script executed:
# shimmer-text를 모든 파일에서 검색 (CSS가 아닌 다른 형식일 수도 있음)
rg "shimmer-text" src/Repository: swallowedB/b-log
Length of output: 337
🏁 Script executed:
# globals.css 전체 내용 확인
cat -n src/app/globals.cssRepository: swallowedB/b-log
Length of output: 1584
🏁 Script executed:
# tailwind config 파일 확인
fd -e config.ts -e config.js | grep -i tailwindRepository: swallowedB/b-log
Length of output: 78
🏁 Script executed:
# tailwind.config.ts 또는 tailwind.config.js 확인
cat -n tailwind.config.* 2>/dev/null || echo "No tailwind config found"Repository: swallowedB/b-log
Length of output: 409
🏁 Script executed:
# 다른 CSS 파일들도 확인 - shimmer-text가 정의되어 있을 수 있음
cat -n src/styles/components/*.cssRepository: swallowedB/b-log
Length of output: 10898
🏁 Script executed:
# 나머지 CSS 파일 검색
cat -n src/styles/tokens.cssRepository: swallowedB/b-log
Length of output: 1012
🏁 Script executed:
# MDX 스타일 확인
cat -n src/styles/mdx.cssRepository: swallowedB/b-log
Length of output: 3515
🏁 Script executed:
# 유틸리티 CSS 파일들 확인
cat -n src/styles/utilities/*.cssRepository: swallowedB/b-log
Length of output: 3487
🏁 Script executed:
# 전체 repo에서 shimmer 검색
rg "shimmer" --type cssRepository: swallowedB/b-log
Length of output: 42
shimmer-text 클래스 정의가 누락되었습니다.
not-found.tsx 32, 35줄에서 shimmer-text 클래스를 사용하고 있지만, 글로벌 스타일이나 CSS 파일 어디에도 정의되지 않았습니다. 해당 클래스를 src/styles/utilities/ 또는 src/styles/components/에 추가하여 정의해야 합니다.
🤖 Prompt for AI Agents
In `@src/app/not-found.tsx` around lines 5 - 47, The NotFound component uses a
missing CSS utility class shimmer-text; add a definition for .shimmer-text in
your global utilities or components styles (e.g., under your utilities/styles
module) that implements the shimmer effect (background gradient,
background-clip:text or color overlay, keyframes for sliding shimmer, and
animation properties) so the two <p> elements in NotFound render with the
intended animated shimmer; ensure you include the keyframes under the same
stylesheet and export/import the stylesheet so it is applied globally.
| <div | ||
| className={clsx("relative w-[min(440px,90vw)]", className)} | ||
| onClick={(e) => e.stopPropagation()} | ||
| > |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
이벤트 전파 방지용 onClick에 대한 접근성 고려
정적 분석에서 지적된 대로, div에 onClick이 있지만 키보드 이벤트가 없습니다. 이 경우 stopPropagation() 목적이므로 실제 인터랙션은 아니지만, 접근성 도구 경고를 해소하려면 role="presentation"을 추가할 수 있습니다.
♻️ 접근성 속성 추가 제안
<div
className={clsx("relative w-[min(440px,90vw)]", className)}
onClick={(e) => e.stopPropagation()}
+ role="presentation"
>📝 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.
| <div | |
| className={clsx("relative w-[min(440px,90vw)]", className)} | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div | |
| className={clsx("relative w-[min(440px,90vw)]", className)} | |
| onClick={(e) => e.stopPropagation()} | |
| role="presentation" | |
| > |
🧰 Tools
🪛 Biome (2.1.2)
[error] 14-17: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 14-17: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
🤖 Prompt for AI Agents
In `@src/components/common/WipFeatureCard.tsx` around lines 14 - 17, The div in
WipFeatureCard (the JSX element with className={clsx("relative
w-[min(440px,90vw)]", className)} and onClick={(e) => e.stopPropagation()})
currently has an onClick handler but no accessibility role; update that element
to include role="presentation" (and optionally tabIndex={-1} if needed) to
indicate it is non-interactive and silence keyboard/accessibility warnings while
preserving the stopPropagation behavior.
| "h-7 w-7 rounded-full", | ||
| "text-black/20 hover:text-white", | ||
| "hover:bg-black/10 cursor-pointer", | ||
| "transition rotate-80 " |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# rotate-80 클래스 정의 확인
echo "=== 1. CSS 파일에서 rotate-80 검색 ==="
rg -n "rotate-80" --type css 2>/dev/null || echo "No CSS matches found"
echo -e "\n=== 2. tailwind.config 파일에서 rotate 관련 설정 검색 ==="
fd "tailwind.config" -x cat {} \;
echo -e "\n=== 3. 전체 프로젝트에서 rotate-80 검색 ==="
rg -n "rotate-80" 2>/dev/null || echo "No matches found"
echo -e "\n=== 4. 프로젝트 구조 확인 ==="
ls -la | grep -E "tailwind|css"
find . -maxdepth 2 -name "*.config.*" -o -name "tailwind*" 2>/dev/nullRepository: swallowedB/b-log
Length of output: 820
🏁 Script executed:
#!/bin/bash
# WipFeatureCard.tsx에서 rotate-80이 사용된 컨텍스트 확인
sed -n '20,35p' src/components/common/WipFeatureCard.tsxRepository: swallowedB/b-log
Length of output: 517
rotate-80은 정의되지 않은 클래스입니다. Tailwind CSS 기본 rotate 값(rotate-0, rotate-90, rotate-180 등) 중 하나로 수정이 필요합니다.
tailwind.config.ts와 프로젝트의 CSS 파일에서 rotate-80 정의를 찾을 수 없습니다. 현재 코드는 transition 효과는 적용되지만 회전 애니메이션은 작동하지 않습니다. 의도에 맞는 값(예: rotate-90)으로 수정해 주세요.
🤖 Prompt for AI Agents
In `@src/components/common/WipFeatureCard.tsx` at line 28, The class string in
WipFeatureCard.tsx contains an undefined Tailwind class "rotate-80", so the
rotation won't work; replace "rotate-80" with a valid Tailwind rotate utility
(e.g., "rotate-90" or "rotate-45") in the className where "transition rotate-80
" appears (inside the WipFeatureCard component) or add a custom rotate value to
tailwind.config.ts if a non-standard angle is required; ensure the updated
className uses a supported rotate-* utility so the rotation animates as
intended.
| export function WipFeatureOverlay({ isOpen, onClose }: WipFeatureOverlayProps) { | ||
| if (typeof document === "undefined") { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
하이드레이션 불일치 가능성
typeof document === "undefined" 체크는 SSR 에러를 방지하지만, 서버에서는 null을 반환하고 클라이언트에서는 Portal 콘텐츠를 반환하여 하이드레이션 불일치가 발생할 수 있습니다.
useEffect + useState를 사용하여 클라이언트 마운트 후에만 Portal을 렌더링하는 방식이 더 안전합니다.
♻️ 권장 수정안
+"use client";
+
+import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { createPortal } from "react-dom";
import { WipFeatureCard } from "./WipFeatureCard";
interface WipFeatureOverlayProps {
isOpen: boolean;
onClose: () => void;
}
export function WipFeatureOverlay({ isOpen, onClose }: WipFeatureOverlayProps) {
- if (typeof document === "undefined") {
- return null;
- }
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ if (!mounted) {
+ return null;
+ }
return createPortal(📝 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.
| export function WipFeatureOverlay({ isOpen, onClose }: WipFeatureOverlayProps) { | |
| if (typeof document === "undefined") { | |
| return null; | |
| } | |
| "use client"; | |
| import { useEffect, useState } from "react"; | |
| import { AnimatePresence, motion } from "framer-motion"; | |
| import { createPortal } from "react-dom"; | |
| import { WipFeatureCard } from "./WipFeatureCard"; | |
| interface WipFeatureOverlayProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| } | |
| export function WipFeatureOverlay({ isOpen, onClose }: WipFeatureOverlayProps) { | |
| const [mounted, setMounted] = useState(false); | |
| useEffect(() => { | |
| setMounted(true); | |
| }, []); | |
| if (!mounted) { | |
| return null; | |
| } | |
| return createPortal( | |
| // ... rest of component | |
| ); | |
| } |
🤖 Prompt for AI Agents
In `@src/components/common/WipFeatureOverlay.tsx` around lines 12 - 15, The
component WipFeatureOverlay returns null on server via a typeof document ===
"undefined" check which causes hydration mismatch; replace that check by
tracking client mount with useState/useEffect (e.g., const [mounted, setMounted]
= useState(false); useEffect(() => setMounted(true), [])) and only render the
Portal when mounted is true (and isOpen as before), removing the direct typeof
document guard so server renders the same markup as the client until the client
mount toggles rendering.
| <div | ||
| className="fixed inset-0 z-60 flex items-center justify-center" | ||
| onClick={onClose} | ||
| > |
There was a problem hiding this comment.
접근성 개선 필요 (키보드 이벤트 및 ARIA 속성)
정적 분석에서 지적한 대로, 백드롭 div에 onClick이 있지만 키보드 이벤트 핸들러가 없습니다. 모달/오버레이의 표준 UX로 Escape 키로 닫을 수 있어야 합니다.
🔧 접근성 개선 코드
+import { useEffect, useState, useCallback } from "react"; export function WipFeatureOverlay({ isOpen, onClose }: WipFeatureOverlayProps) {
+ const handleKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ if (e.key === "Escape") {
+ onClose();
+ }
+ },
+ [onClose]
+ );
+
+ useEffect(() => {
+ if (isOpen) {
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }
+ }, [isOpen, handleKeyDown]);백드롭 div에 role과 aria 속성 추가:
<div
- className="fixed inset-0 z-60 flex items-center justify-center"
- onClick={onClose}
+ role="dialog"
+ aria-modal="true"
+ aria-label="준비중 기능 안내"
+ className="fixed inset-0 z-60 flex items-center justify-center bg-black/30"
+ onClick={onClose}
+ onKeyDown={(e) => e.key === "Escape" && onClose()}
>🧰 Tools
🪛 Biome (2.1.2)
[error] 20-23: Static Elements should not be interactive.
To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.
(lint/a11y/noStaticElementInteractions)
[error] 20-23: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.
Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.
(lint/a11y/useKeyWithClickEvents)
🤖 Prompt for AI Agents
In `@src/components/common/WipFeatureOverlay.tsx` around lines 20 - 23, The
backdrop div in WipFeatureOverlay (the element with className "fixed inset-0
z-60 flex items-center justify-center" and onClick={onClose}) lacks keyboard
handling and ARIA attributes; add an onKeyDown handler that listens for Escape
and calls onClose, make the element focusable with tabIndex={0}, and add
appropriate ARIA attributes (e.g., role="button" or role="presentation"
depending on surrounding markup, and aria-label like "Close overlay" or use
role="dialog" + aria-modal="true" on the modal container if this is the dialog
root) so keyboard users can close the overlay. Ensure the onClose prop is reused
and that focus is moved into/out of the overlay as appropriate (e.g., set
initial focus on mount or return focus on unmount) to complete accessible
behavior.
요약
NotFound페이지 구현,WIP Card(준비중 안내) 추가, 임시 일러스트 적용변경 내용
app/not-found.tsx추가 → 404 페이지 구현WipFeatureCard컴포넌트 추가 (준비중 안내)WipFeatureOverlay구현 → 준비중 기능 오버레이 표시public/error/wip-card.svg) 적용useWipFeatureNotice) 연결스크린샷/동영상 (선택)
테스트
관련 이슈
close #48
Summary by CodeRabbit
릴리스 노트
새 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.