Skip to content

[feature] 웹뷰 바텀 네비게이션 + 셸 레이아웃#1631

Open
seongwon030 wants to merge 2 commits into
feature/web-shell-migrationfrom
feature/#1622-add-webview-bottom-nav-MOA-925
Open

[feature] 웹뷰 바텀 네비게이션 + 셸 레이아웃#1631
seongwon030 wants to merge 2 commits into
feature/web-shell-migrationfrom
feature/#1622-add-webview-bottom-nav-MOA-925

Conversation

@seongwon030
Copy link
Copy Markdown
Member

@seongwon030 seongwon030 commented Jun 1, 2026

요약

홍보·알림 위치 ABCD 실험(MOA-924)의 선행 조건인 바텀 네비 웹뷰 이관의 첫 단계. 네이티브 탭바를 대체할 웹 바텀 네비게이션과 셸 레이아웃을 추가합니다.

변경

  • WebviewBottomNav(홈/구독/메뉴) + webviewBottomNavConfig 신규 — 상단 필터칩(동아리/홍보)과 별개 리스트
  • WebviewLayout에 영구 바텀바 장착, 콘텐츠 하단 패딩
  • /webview/subscribed, /webview/menu 라우트 추가 (페이지는 후속 [feature] MOA-926 구독 동아리 목록 페이지 구현 #1623/#1624에서 구현, 현재 placeholder)
  • 동아리 상세에서는 바텀바 숨김, 홈 탭은 main/promotions에서 활성

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 웹뷰에 하단 탭 네비게이션 추가 (홈, 구독, 메뉴)
    • 구독 및 메뉴 페이지 추가로 기능 확장
    • 탭 선택 시 활성 상태 시각적 표시

- WebviewBottomNav(홈/구독/메뉴) 신규, 상단 필터칩(동아리/홍보)과 별개 config
- WebviewLayout에 영구 바텀바 장착 + 콘텐츠 하단 패딩
- /webview/subscribed, /webview/menu 라우트 추가 (페이지는 placeholder, 후속 구현)
- 동아리 상세에서 바텀바 숨김, 홈 탭은 main/promotions에서 활성
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Jun 1, 2026 2:24pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Warning

Review limit reached

@seongwon030, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcbcde66-9a35-4f77-80b1-ef1177de0792

📥 Commits

Reviewing files that changed from the base of the PR and between d91f054 and 9b3cca4.

📒 Files selected for processing (4)
  • frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts
  • frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx
  • frontend/src/pages/WebviewLayout/WebviewLayout.styles.ts
  • frontend/src/pages/WebviewLayout/WebviewLayout.tsx

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

이 PR은 웹뷰의 하단 탭 네비게이션 기능을 완성합니다. styled-components로 고정 레이아웃 스타일을 정의하고, React Router 기반 라우팅 로직을 구현하며, WebviewLayout에 통합하고, 새로운 페이지 라우트(구독/메뉴)를 추가합니다.

Changes

웹뷰 하단 네비게이션

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의 렌더링 구조가 변경되어 OutletContentArea 스타일 컴포넌트로 감싸고, 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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@seongwon030 seongwon030 changed the title feat: 웹뷰 바텀 네비게이션 + 셸 레이아웃 (#1622) [feature] 웹뷰 바텀 네비게이션 + 셸 레이아웃 Jun 1, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread frontend/src/pages/WebviewLayout/WebviewLayout.tsx Outdated
Comment thread frontend/src/pages/WebviewLayout/WebviewLayout.styles.ts Outdated
Comment thread frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (6)
frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx (2)

12-18: 💤 Low value

isActive 함수의 타입 안전성 개선 제안

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 win

ThemeProvider를 통한 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 win

ThemeProvider를 통한 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 value

TypeScript 타입 어노테이션 추가를 고려해보세요.

플레이스홀더이긴 하지만, 명시적인 타입 어노테이션을 추가하면 타입 안정성이 향상됩니다.

♻️ 타입 어노테이션 추가 예시
-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 value

TypeScript 타입 어노테이션 추가를 고려해보세요.

플레이스홀더이긴 하지만, 명시적인 타입 어노테이션을 추가하면 타입 안정성이 향상됩니다.

♻️ 타입 어노테이션 추가 예시
-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

📥 Commits

Reviewing files that changed from the base of the PR and between b7439f5 and d91f054.

📒 Files selected for processing (8)
  • frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.styles.ts
  • frontend/src/components/common/WebviewBottomNav/WebviewBottomNav.tsx
  • frontend/src/pages/WebviewLayout/WebviewLayout.styles.ts
  • frontend/src/pages/WebviewLayout/WebviewLayout.tsx
  • frontend/src/pages/WebviewMenuPage/WebviewMenuPage.tsx
  • frontend/src/pages/WebviewSubscribedPage/WebviewSubscribedPage.tsx
  • frontend/src/routes/webviewBottomNavConfig.ts
  • frontend/src/routes/webviewRoutes.tsx

- 동아리 상세에서 바텀바 숨김 + 하단 패딩 제거를 WebviewLayout에서 일괄 제어
- WebviewBottomNav 중복 경로 체크 제거, isActive 타입을 WebviewBottomNavPath로
- NavButton에 aria-current 추가, 스타일 theme를 ThemeProvider accessor로
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant