[feature] 웹뷰 바텀 네비게이션 + 셸 레이아웃#1631
Conversation
- WebviewBottomNav(홈/구독/메뉴) 신규, 상단 필터칩(동아리/홍보)과 별개 config - WebviewLayout에 영구 바텀바 장착 + 콘텐츠 하단 패딩 - /webview/subscribed, /webview/menu 라우트 추가 (페이지는 placeholder, 후속 구현) - 동아리 상세에서 바텀바 숨김, 홈 탭은 main/promotions에서 활성
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 11 minutes and 39 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
Warning
|
| Layer / File(s) | Summary |
|---|---|
스타일링과 레이아웃 기초 frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts, frontend/src/pages/WebviewLayout/WebviewLayout.styles.ts |
하단 네비게이션 컨테이너는 position: fixed로 화면 하단에 고정되고, safe-area-inset-bottom을 고려한 높이 계산과 패딩을 적용합니다. 네비게이션 버튼은 $isActive 상태에 따라 글꼴 굵기와 색상이 동적으로 변경됩니다. ContentArea는 하단 네비게이션 높이만큼의 패딩을 추가하여 콘텐츠 겹침을 방지합니다. |
네비게이션 컴포넌트와 설정 frontend/src/routes/webviewBottomNavConfig.ts, frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx |
WEBVIEW_BOTTOM_NAV 설정 배열이 홈/구독/메뉴 탭을 정의하고, WebviewBottomNavPath 타입으로 경로의 타입 안전성을 제공합니다. WebviewBottomNav 컴포넌트는 useLocation/useNavigate 훅으로 현재 경로를 감지하고, /webview/club 경로에서는 네비게이션을 숨기며, 홈 탭은 /webview/main과 /webview/promotions 모두에서 활성화됩니다. |
레이아웃 통합 frontend/src/pages/WebviewLayout/WebviewLayout.tsx |
WebviewLayout의 렌더링 구조가 변경되어 Outlet을 ContentArea 스타일 컴포넌트로 감싸고, WebviewBottomNav 컴포넌트를 하단에 추가하여 전체 레이아웃을 구성합니다. |
라우트 및 페이지 컴포넌트 frontend/src/routes/webviewRoutes.tsx, frontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsx, frontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsx |
/webview/subscribed와 /webview/menu 라우트가 추가되며, 각 라우트의 element를 ContentErrorBoundary로 감싼 후 WebviewSubscribedPage와 WebviewMenuPage 컴포넌트를 할당합니다. 두 페이지는 현재 플레이스홀더 상태로 텍스트만 표시합니다. |
Sequence Diagram(s)
sequenceDiagram
participant Router as React Router
participant WebviewBottomNav
participant isActive as활성_판단_로직
participant NavButton as렌더링_버튼
Router->>WebviewBottomNav: pathname 제공
WebviewBottomNav->>WebviewBottomNav: /webview/club 시작 확인
alt club 경로
WebviewBottomNav-->>Router: null 반환
else 나머지 경로
WebviewBottomNav->>isActive: 각 탭 경로 활성 여부 확인
isActive-->>WebviewBottomNav: boolean 반환
WebviewBottomNav->>NavButton: $isActive props 전달
NavButton->>Router: navigate(tab.path) 실행
end
Estimated code review effort
🎯 2 (Simple) | ⏱️ ~12 minutes
Possibly related issues
- [feature] MOA-925 웹 바텀 네비게이션 + 셸 레이아웃 구현 #1622: 웹뷰 하단 네비게이션 구현 및 셸 레이아웃 개선 작업으로, 이 PR의 모든 기능 변경(styled-components, 라우팅 로직, 레이아웃 통합)이 직접 대응됩니다.
Possibly related PRs
- Moadong/moadong#1506: 이전 PR에서 도입한 WebviewLayout의 Outlet 렌더링 구조를 이 PR이 ContentArea 래핑과 WebviewBottomNav 추가로 확장하므로, 같은 컴포넌트에 대한 연속적인 개선입니다.
- Moadong/moadong#1116: 이 PR의 WebviewBottomNav가
/webview/club경로에서 네비게이션을 숨기는 조건을 두었고, 해당 PR이/webview/club/:clubId라우트를 구현하여 코드 경로가 맞물립니다.
Suggested labels
✨ Feature, 💻 FE
Suggested reviewers
- suhyun113
- lepitaaar
- oesnuj
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | PR 제목이 변경사항의 핵심을 명확하게 반영하고 있습니다. 웹뷰 바텀 네비게이션과 셸 레이아웃 추가라는 주요 변경사항을 간결하게 표현했습니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| 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. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
✏️ 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
feature/#1622-add-webview-bottom-nav-MOA-925
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.
Code Review
This pull request introduces a webview bottom navigation bar (WebviewBottomNav) along with placeholder pages for 'Subscribed' and 'Menu' tabs, updating the webview layout and routing configuration. The review feedback highlights an issue where bottom padding is always applied to the content area, even when the bottom navigation is hidden (e.g., on the club detail page). To resolve this, it is recommended to manage the bottom navigation's visibility state in WebviewLayout and conditionally apply the padding to ContentArea via a prop, which also allows removing the duplicate route-checking logic from WebviewBottomNav.
There was a problem hiding this comment.
🧹 Nitpick comments (6)
frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx (2)
12-18: 💤 Low valueisActive 함수의 타입 안전성 개선 제안
isActive함수의 매개변수를WebviewBottomNavPath타입으로 지정하면 더 강력한 타입 안전성을 확보할 수 있습니다.♻️ 타입 안전성 개선 제안
+import type { WebviewBottomNavPath } from '`@/routes/webviewBottomNavConfig`'; - const isActive = (path: string) => { + const isActive = (path: WebviewBottomNavPath) => { // 홈 탭은 동아리 목록(/webview/main)과 홍보(/webview/promotions)에서 활성. if (path === '/webview/main') { return pathname === '/webview/main' || pathname === '/webview/promotions'; } return pathname === path; };🤖 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 `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx` around lines 12 - 18, The isActive function currently accepts a plain string; change its parameter to the WebviewBottomNavPath union type to improve type safety and prevent invalid path values. Update the signature of isActive to accept (path: WebviewBottomNavPath) and ensure any callers pass values typed as WebviewBottomNavPath (or are cast/converted), keeping the existing logic that checks pathname === path and the special-case for '/webview/main' alongside pathname === '/webview/promotions'.
23-30: ⚡ Quick win접근성 개선을 위한 ARIA 속성 추가 권장
네비게이션 버튼에
aria-current속성을 추가하면 스크린 리더 사용자에게 현재 활성화된 페이지를 명확히 알릴 수 있습니다.♻️ 접근성 개선 제안
<Styled.NavButton key={tab.path} type='button' $isActive={isActive(tab.path)} + aria-current={isActive(tab.path) ? 'page' : undefined} onClick={() => navigate(tab.path)} > {tab.label} </Styled.NavButton>🤖 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 `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx` around lines 23 - 30, The NavButton currently lacks an ARIA indicator of the active page; update the Styled.NavButton render (the element created by Styled.NavButton where isActive(tab.path) and onClick={() => navigate(tab.path)} are used) to include an aria-current attribute when the tab is active—e.g., set aria-current="page" if isActive(tab.path) is true and omit or set undefined otherwise—so screen readers can announce the currently active page.frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts (2)
2-2: ⚡ Quick winThemeProvider를 통한 theme 접근 권장
현재 theme을 직접 import하여 사용하고 있습니다만, 코딩 가이드라인에 따르면 styled-components는 ThemeProvider를 통해 theme에 접근해야 합니다. 이렇게 하면 런타임 테마 전환이 가능하고 일관된 패턴을 유지할 수 있습니다.
♻️ ThemeProvider 패턴으로 변경 제안
import styled from 'styled-components'; -import { theme } from '`@/styles/theme`'; export const WEBVIEW_BOTTOM_NAV_HEIGHT = 56; export const BottomNavContainer = styled.nav` position: fixed; left: 0; right: 0; bottom: 0; height: calc(${WEBVIEW_BOTTOM_NAV_HEIGHT}px + env(safe-area-inset-bottom)); padding-bottom: env(safe-area-inset-bottom); display: flex; - background-color: ${theme.colors.base.white}; - border-top: 1px solid ${theme.colors.gray[300]}; + background-color: ${({ theme }) => theme.colors.base.white}; + border-top: 1px solid ${({ theme }) => theme.colors.gray[300]}; z-index: 100; `;코딩 가이드라인: "Use styled-components for styling with ThemeProvider for theme access"
Also applies to: 14-15
🤖 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 `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts` at line 2, The file WebviewBottomNav.styles.ts imports theme directly; change to use ThemeProvider-backed theme access by removing the direct import and referencing theme from styled-components props (e.g., use ${({ theme }) => theme.xxx} inside the styled components defined in this file), update any type annotations to DefaultTheme if needed, and ensure all places that currently use the imported theme (including the occurrences around lines referenced 14-15) are replaced to read from props.theme so runtime theme switching and the project ThemeProvider pattern are respected.
29-30: ⚡ Quick winThemeProvider를 통한 theme 접근 권장
NavButton에서도 theme을 props를 통해 접근하도록 변경하는 것이 좋습니다.
♻️ ThemeProvider 패턴으로 변경 제안
export const NavButton = styled.button<{ $isActive: boolean }>` flex: 1; display: flex; align-items: center; justify-content: center; border: none; background: none; cursor: pointer; font-size: 12px; font-weight: ${({ $isActive }) => ($isActive ? 600 : 400)}; - color: ${({ $isActive }) => - $isActive ? theme.colors.primary[900] : theme.colors.gray[600]}; + color: ${({ $isActive, theme }) => + $isActive ? theme.colors.primary[900] : theme.colors.gray[600]}; `;코딩 가이드라인: "Use styled-components for styling with ThemeProvider for theme access"
🤖 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 `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts` around lines 29 - 30, The NavButton styled component in WebviewBottomNav.styles.ts is directly referencing a global theme variable; change it to read theme from the styled-components props (i.e., use the callback signature that receives both $isActive and theme) so the color expression uses props.theme.colors... instead of a global theme, and if using TypeScript ensure the styled component’s props include DefaultTheme typing so theme is correctly typed.frontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsx (1)
2-4: 💤 Low valueTypeScript 타입 어노테이션 추가를 고려해보세요.
플레이스홀더이긴 하지만, 명시적인 타입 어노테이션을 추가하면 타입 안정성이 향상됩니다.
♻️ 타입 어노테이션 추가 예시
-const WebviewSubscribedPage = () => { +const WebviewSubscribedPage = (): JSX.Element => { return <div>구독</div>; };또는:
+import type { FC } from 'react'; + -const WebviewSubscribedPage = () => { +const WebviewSubscribedPage: FC = () => { return <div>구독</div>; };🤖 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 `@frontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsx` around lines 2 - 4, The component WebviewSubscribedPage lacks an explicit TypeScript type annotation; add a return/type annotation (for example annotate the function as React.FC or give it an explicit return type of JSX.Element) and ensure React is imported if your linting/tsconfig requires it so WebviewSubscribedPage is typed explicitly for better type safety.frontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsx (1)
2-4: 💤 Low valueTypeScript 타입 어노테이션 추가를 고려해보세요.
플레이스홀더이긴 하지만, 명시적인 타입 어노테이션을 추가하면 타입 안정성이 향상됩니다.
♻️ 타입 어노테이션 추가 예시
-const WebviewMenuPage = () => { +const WebviewMenuPage = (): JSX.Element => { return <div>메뉴</div>; };또는:
+import type { FC } from 'react'; + -const WebviewMenuPage = () => { +const WebviewMenuPage: FC = () => { return <div>메뉴</div>; };🤖 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 `@frontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsx` around lines 2 - 4, Add an explicit TypeScript type annotation to the component declaration to improve type safety: annotate the WebviewMenuPage constant either as a React component type (e.g., React.FC) or give its function an explicit return type (e.g., (): JSX.Element) and ensure any required React types are imported; update the WebviewMenuPage declaration accordingly.
🤖 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.
Nitpick comments:
In `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts`:
- Line 2: The file WebviewBottomNav.styles.ts imports theme directly; change to
use ThemeProvider-backed theme access by removing the direct import and
referencing theme from styled-components props (e.g., use ${({ theme }) =>
theme.xxx} inside the styled components defined in this file), update any type
annotations to DefaultTheme if needed, and ensure all places that currently use
the imported theme (including the occurrences around lines referenced 14-15) are
replaced to read from props.theme so runtime theme switching and the project
ThemeProvider pattern are respected.
- Around line 29-30: The NavButton styled component in
WebviewBottomNav.styles.ts is directly referencing a global theme variable;
change it to read theme from the styled-components props (i.e., use the callback
signature that receives both $isActive and theme) so the color expression uses
props.theme.colors... instead of a global theme, and if using TypeScript ensure
the styled component’s props include DefaultTheme typing so theme is correctly
typed.
In `@frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx`:
- Around line 12-18: The isActive function currently accepts a plain string;
change its parameter to the WebviewBottomNavPath union type to improve type
safety and prevent invalid path values. Update the signature of isActive to
accept (path: WebviewBottomNavPath) and ensure any callers pass values typed as
WebviewBottomNavPath (or are cast/converted), keeping the existing logic that
checks pathname === path and the special-case for '/webview/main' alongside
pathname === '/webview/promotions'.
- Around line 23-30: The NavButton currently lacks an ARIA indicator of the
active page; update the Styled.NavButton render (the element created by
Styled.NavButton where isActive(tab.path) and onClick={() => navigate(tab.path)}
are used) to include an aria-current attribute when the tab is active—e.g., set
aria-current="page" if isActive(tab.path) is true and omit or set undefined
otherwise—so screen readers can announce the currently active page.
In `@frontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsx`:
- Around line 2-4: Add an explicit TypeScript type annotation to the component
declaration to improve type safety: annotate the WebviewMenuPage constant either
as a React component type (e.g., React.FC) or give its function an explicit
return type (e.g., (): JSX.Element) and ensure any required React types are
imported; update the WebviewMenuPage declaration accordingly.
In `@frontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsx`:
- Around line 2-4: The component WebviewSubscribedPage lacks an explicit
TypeScript type annotation; add a return/type annotation (for example annotate
the function as React.FC or give it an explicit return type of JSX.Element) and
ensure React is imported if your linting/tsconfig requires it so
WebviewSubscribedPage is typed explicitly for better type safety.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0cacf35d-0671-4a54-b78e-86469403606e
📒 Files selected for processing (8)
frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.tsfrontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsxfrontend/src/pages/WebviewLayout/WebviewLayout.styles.tsfrontend/src/pages/WebviewLayout/WebviewLayout.tsxfrontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsxfrontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsxfrontend/src/routes/webviewBottomNavConfig.tsfrontend/src/routes/webviewRoutes.tsx
- 동아리 상세에서 바텀바 숨김 + 하단 패딩 제거를 WebviewLayout에서 일괄 제어 - WebviewBottomNav 중복 경로 체크 제거, isActive 타입을 WebviewBottomNavPath로 - NavButton에 aria-current 추가, 스타일 theme를 ThemeProvider accessor로
요약
홍보·알림 위치 ABCD 실험(MOA-924)의 선행 조건인 바텀 네비 웹뷰 이관의 첫 단계. 네이티브 탭바를 대체할 웹 바텀 네비게이션과 셸 레이아웃을 추가합니다.
변경
WebviewBottomNav(홈/구독/메뉴) +webviewBottomNavConfig신규 — 상단 필터칩(동아리/홍보)과 별개 리스트WebviewLayout에 영구 바텀바 장착, 콘텐츠 하단 패딩/webview/subscribed,/webview/menu라우트 추가 (페이지는 후속 [feature] MOA-926 구독 동아리 목록 페이지 구현 #1623/#1624에서 구현, 현재 placeholder)Summary by CodeRabbit
릴리스 노트