Skip to content

[feature] 상세 페이지 모바일 탑바 웹뷰로 이관 및 리액트 네이티브 API 연동 구조 도입#1090

Merged
oesnuj merged 26 commits intodevelop-fefrom
feature/#1027-mobile-detail-webview-topbar-MOA-507
Jan 25, 2026
Merged

[feature] 상세 페이지 모바일 탑바 웹뷰로 이관 및 리액트 네이티브 API 연동 구조 도입#1090
oesnuj merged 26 commits intodevelop-fefrom
feature/#1027-mobile-detail-webview-topbar-MOA-507

Conversation

@oesnuj
Copy link
Member

@oesnuj oesnuj commented Jan 23, 2026

#️⃣ 연관된 이슈

#1027

📌 작업 개요

모바일 상세페이지 웹뷰 탑바 및 UI 개선

동아리 상세페이지의 모바일/웹뷰 환경에서 사용자 경험을 개선하기 위한 UI/UX 업데이트입니다.

리액트 네이티브와 함께 작업을 진행했습니다
리액트 네이티브 PR : Moadong/moadong-react-native#5



변경사항

1. 웹뷰 모바일 탑바 구현

새로운 컴포넌트: ClubDetailTopBar

파일 설명
ClubDetailTopBar.tsx 모바일 전용 탑바 컴포넌트
ClubDetailTopBar.styles.ts 탑바 스타일 정의

주요 기능:

  • 🔙 뒤로가기 버튼: 웹뷰에서는 앱에 네비게이션 요청, 웹에서는 브라우저 히스토리 사용
  • 🔔 알림 구독 토글: 앱 환경에서만 표시, 동아리 알림 구독/해제 기능
  • 📍 스크롤 연동 헤더: 스크롤 위치에 따라 배경색/그림자 동적 변화Q
  • 📑 탭바 고정: 일정 스크롤 이후 탭바가 고정되어 표시
// 스크롤 임계값 상수
const SCROLL_THRESHOLD = {
  HEADER_VISIBLE: 0,   // 헤더 배경/타이틀 표시
  TAB_STICKY: 360,      // 탭바 고정
} as const;

웹뷰 브릿지 유틸리티 추가

파일 설명
webviewBridge.ts 앱-웹뷰 통신 유틸리티

Facade 패턴 적용:

// 액션 헬퍼 함수 (Facade 패턴)
export const requestNavigateBack = (): boolean => {...}
export const requestNotificationSubscribe = (clubId: string, clubName?: string): boolean => {...}
export const requestNotificationUnsubscribe = (clubId: string): boolean => {...}

지원하는 메시지 타입:

  • NAVIGATE_BACK: 앱에 뒤로가기 요청
  • NOTIFICATION_SUBSCRIBE: 알림 구독 요청
  • NOTIFICATION_UNSUBSCRIBE: 알림 구독 해제
sequenceDiagram
    participant Web as React(모바일웹)
    participant Bridge as WebviewBridge
    participant App as App(WebView)
    
    Note over Web, App: 알림 버튼 클릭 시 흐름
    
    Web->>Bridge: 1. requestNotificationSubscribe() 호출
    Bridge->>Bridge: 2. isInAppWebView() 환경 체크
    
    alt ✅ 앱 환경 (In-App)
        Bridge->>App: 3. postMessage({ type: 'NOTIFICATION_SUBSCRIBE' }) 전송
        Note right of Bridge: 성공 반환 (true)
        Bridge-->>Web: 4. return true
        Web->>Web: 5. 아이콘 활성화 (Color: primary)
    else ❌ 웹 브라우저 (Not In-App)
        Note left of Bridge: 메시지 전송 안 함 (무시)
        Bridge-->>Web: return false
        Web->>Web: UI 변경 없음 (Color: Gray 유지)
    end
Loading

2. 상세페이지 UI 수정

프로필 카드 UI 개선

파일 설명
ClubProfileCard.tsx 프로필 카드 컴포넌트
ClubProfileCard.styles.ts 프로필 카드 스타일
  • BackButton (뒤로가기 버튼) 제거 - 기존 프로필 카드 내 뒤로가기 버튼을 새로운 TopBar로 이동
  • IntroDescription 모바일 폰트 크기 제거 - 모바일에서 12px로 줄이던 스타일 제거

스크롤 유틸리티 훅 추가

파일 설명
useScrollTo.ts 스크롤 위치 제어 훅

추가 배경
기존에는 상단으로만 스크롤하는 훅만 존재했으나, 특정 위치로 이동시키는 기능이 필요해 확장성을 고려하여 범용 스크롤 훅을 추가함.

활용 사례

  • 소개내용/활동사진 탭이 고정된 상태에서 탭 클릭 시, 해당 컨텐츠의 맨 윗부분이 보이도록 스크롤 이동

기능:

  • scrollToTop(): 페이지 최상단으로 스크롤
  • scrollToElement(): 특정 엘리먼트로 오프셋 포함 스크롤
  • scrollTo(): 커스텀 위치로 스크롤

3. 인프라/설정 변경

SVGR 플러그인 설정

파일 설명
vite-env.d.ts Vite 환경 타입 정의
/// <reference types="vite-plugin-svgr/client" />

이유

  1. SVG를 React 컴포넌트로 직접 import(*.svg?react)할 수 있도록 설정
  2. 알림 아이콘의 fill 속성을 컴포넌트에서 동적으로 스타일링하기 위함 (회색 ↔ 주황색 토글)

모바일 아이콘 에셋 추가

파일 설명
prev_button_icon.svg 뒤로가기 버튼 아이콘
next_button_icon.svg 다음 버튼 아이콘
notification_icon.svg 알림 아이콘



🐛 트러블슈팅 (Fixes)

커밋 수정 내용 이유
fix 상세페이지 프로필 카드에서 오류가기 제거 foreignObject 사용으로 인한 SVG 렌더링 이슈 해결
fix ClubDetailTopBar에 safe area 패딩 적용 iOS 노치 영역에서 UI 잘림 방지
fix 이전/다음 버튼 아이콘 크기 오류 수정 foreignObject 제거로 SVG 직접 렌더링 방식 변경
fix 고정된 탑바에서만 스크롤 이동하도록 수정 탭 클릭 시 불필요한 스크롤 동작 방지
fix 탭 클릭 시 스크롤 위치 보정을 위해 TOP_BAR_HEIGHT 값 조정 탭바 고정 시 컨텐츠가 탑바에 가려지는 문제 해결
fix 상세 페이지 직접 접근 시 오류가기 동작 개선 히스토리 스택이 없는 경우(직접 URL 진입) 메인으로 이동하도록 처리



✅ 테스트 체크리스트

  • 모바일 환경에서 상세페이지 탑바 표시 확인
  • 스크롤 시 헤더 배경색/그림자 전환 확인
  • 탭 클릭 시 컨텐츠 영역으로 스크롤 동작 확인
  • 웹뷰 환경에서 뒤로가기 버튼 동작 확인
  • 알림 구독/해제 토글 동작 확인 (웹뷰 환경)

결과 화면

웹 모바일의 상세페이지

default.mp4

앱의 상세페이지

2026-01-24.143236.mp4

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • SVG 파일을 React 컴포넌트로 직접 임포트 가능
    • 클럽 상세 페이지에 고정 상단 바 추가 (모바일/태블릿)
    • 탭 선택 시 자동 스크롤 기능 추가
    • 웹뷰 기반 알림 구독/구독 해제 기능 추가
    • 향상된 스크롤 유틸리티 제공
  • 개선사항

    • 탭 클릭 동작 통합 및 최적화
    • 불필요한 UI 요소 제거로 사용성 개선

✏️ Tip: You can customize this high-level summary in your review settings.

- Vite 환경에서 SVG를 React 컴포넌트로 바로 import(`*.svg?react`)할 수 있도록 `vite-plugin-svgr` 플러그인 설치 및 설정
- TypeScript가 확장자를 인식할 수 있도록 타입 정의(`vite-env.d.ts`) 추가
- 앱 환경(`isInApp`)에서만 알림 버튼이 표시되도록 처리
- 앱 환경(React Native WebView)과 통신하기 위한 postMessage 래퍼 함수 구현
- 알림 구독/취소 및 뒤로가기 요청 기능
- 기존 헤더를 새로 구현된 MobileHeader로 교체
- 스크롤 인터랙션 및 탭 네비게이션 연동
@oesnuj oesnuj self-assigned this Jan 23, 2026
@oesnuj oesnuj added ✨ Feature 기능 개발 💻 FE Frontend labels Jan 23, 2026
@vercel
Copy link

vercel bot commented Jan 23, 2026

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

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Jan 25, 2026 7:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Warning

Rate limit exceeded

@oesnuj has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 10 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

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. Received: "**" 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

모바일 웹뷰용 ClubDetailTopBar 컴포넌트, 타입 안전 WebView 브리지(webviewBridge), 재사용 가능한 스크롤 훅(useScrollTo), SVGR(vite-plugin-svgr) 설정 및 이에 연관된 ClubDetailPage 탭/스크롤 조정과 프로필 카드의 뒤로가기 제거가 추가되었습니다.

Changes

Cohort / File(s) 변경 요약
Vite / SVGR 설정
frontend/config/vite.config.ts, frontend/package.json, frontend/src/vite-env.d.ts
vite-plugin-svgr 의존성 추가 및 Vite 플러그인 등록, svgr용 ambient 타입 참조 추가
WebView 브리지 유틸
frontend/src/utils/webviewBridge.ts
WebView↔네이티브 메시지 타입 정의 및 postMessageToApp과 페이사드 함수(requestNavigateBack, requestNotificationSubscribe, requestNotificationUnsubscribe) 추가
ClubDetail 탑바 컴포넌트
frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx, .../ClubDetailTopBar.styles.ts
모바일/웹뷰 전용 상단바 컴포넌트 추가 (뒤로가기, 알림 토글, 탭바, 스크롤 기반 가시성/고정)
ClubDetailPage 통합 변경
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx, frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts
모바일/태블릿 감지에 따른 조건부 TopBar 렌더링, 탭 클릭을 URL 검색 파라미터로 통합(replace:true), 탭 전환 시 콘텐츠로 스크롤 정렬 로직 추가
프로필 카드: 뒤로가기 제거
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.tsx, .../ClubProfileCard.styles.ts
BackButton 컴포넌트 및 관련 내비게이션 핸들러/스타일 제거, IntroDescription 모바일 폰트 오버라이드 제거
스크롤 훅 및 버튼 리팩토링
frontend/src/hooks/Scroll/useScrollTo.ts, frontend/src/hooks/Scroll/ScrollToTop.tsx, frontend/src/components/common/ScrollToTopButton/ScrollToTopButton.tsx
useScrollTo 훅 추가 및 기존 직접 window.scroll 호출을 훅으로 대체 (scrollToTop, scrollToElement 등)
스타일 조정
frontend/src/pages/ClubDetailPage/components/..., frontend/src/pages/ClubDetailPage/ClubDetailPage.styles.ts
TabButton 높이(26px) 추가 및 일부 스타일/컴포넌트(export) 제거

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as ClubDetailPage
    participant TopBar as ClubDetailTopBar
    participant Bridge as WebViewBridge
    participant Native as NativeApp

    User->>TopBar: 뒤로가기 버튼 클릭
    TopBar->>Bridge: requestNavigateBack()
    Bridge->>Native: postMessage("{\"type\":\"NAVIGATE_BACK\"}")
    alt In WebView
        Native-->>Bridge: 처리/응답
        Bridge-->>TopBar: true
    else Browser
        Bridge-->>TopBar: false
        TopBar->>Page: react-router navigate(-1) (fallback)
    end
Loading
sequenceDiagram
    actor User
    participant TopBar as ClubDetailTopBar
    participant Bridge as WebViewBridge
    participant Native as NativeApp

    User->>TopBar: 알림 토글 클릭
    TopBar->>Bridge: requestNotificationSubscribe/Unsubscribe(payload)
    Bridge->>Native: postMessage("{\"type\":\"NOTIFICATION_*\",\"payload\":...}")
    alt In WebView
        Native-->>Bridge: 처리 결과
        Bridge-->>TopBar: true
        TopBar->>TopBar: isNotificationActive 업데이트
    else Browser
        Bridge-->>TopBar: false
    end
Loading
sequenceDiagram
    actor User
    participant Page as ClubDetailPage
    participant Scroll as useScrollTo

    User->>Page: 탭 클릭
    Page->>Page: 검색 파라미터 업데이트 (replace:true)
    Page->>Scroll: scrollToElement(contentRef, offset=TOP_BAR_HEIGHT)
    Scroll-->>Page: 스무스 스크롤 수행 (콘텐츠가 TopBar 아래로 보이도록)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 분

Possibly related issues

Possibly related PRs

Suggested reviewers

  • lepitaaar
  • suhyun113
  • seongwon030
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main feature: introducing a mobile top bar for the detail page via WebView and integrating React Native API communication structure, which aligns with the primary changes throughout the changeset.
Linked Issues check ✅ Passed The PR implements all major coding requirements from MOA-507: ClubDetailTopBar component with mobile UI [ClubDetailTopBar.tsx/.styles.ts], webviewBridge for native API communication [webviewBridge.ts], back navigation and notification subscription features, scroll-to utilities [useScrollTo.ts], and mobile environment detection integration.
Out of Scope Changes check ✅ Passed All changes are directly related to MOA-507 objectives: UI components (ClubDetailTopBar, style updates), WebView bridge utilities, scroll utilities supporting the new features, and SVGR configuration enabling icon assets. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

Copy link
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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@frontend/src/pages/ClubDetailPage/components/MobileHeader/MobileHeader.tsx`:
- Around line 33-34: Remove the stale TODO comment above the isInApp assignment
in MobileHeader.tsx: the comment says to restore isInAppWebView() on deploy but
the code already calls isInAppWebView(); delete the TODO line so only the
declaration const isInApp = isInAppWebView(); remains, leaving no misleading
comment in the MobileHeader component.
- Around line 56-64: The click handler only calls requestNotificationSubscribe
and never unsubscribes; update handleNotificationClick to perform a toggle:
check the current isNotificationActive state and call
requestNotificationUnsubscribe(clubId, clubName) when true or
requestNotificationSubscribe(clubId, clubName) when false, then update
setIsNotificationActive based on the returned success value from the invoked
function; ensure you reference the existing requestNotificationUnsubscribe and
requestNotificationSubscribe functions and only flip state when the call returns
success.
🧹 Nitpick comments (5)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

64-81: 탭 설정을 상수로 추출하는 것을 고려하세요.

tabs 배열이 렌더링마다 새로 생성됩니다. 성능에 큰 영향은 없지만, 컴포넌트 외부에 상수로 추출하면 가독성과 재사용성이 향상됩니다.

♻️ 제안된 리팩토링
+const DETAIL_TABS = [
+  { key: TAB_TYPE.INTRO, label: '소개내용' },
+  { key: TAB_TYPE.PHOTOS, label: '활동사진' },
+] as const;
+
 const ClubDetailPage = () => {
   // ...
   {showMobileHeader && (
     <MobileHeader
       clubId={clubId || ''}
       clubName={clubDetail.name}
-      tabs={[
-        { key: TAB_TYPE.INTRO, label: '소개내용' },
-        { key: TAB_TYPE.PHOTOS, label: '활동사진' },
-      ]}
+      tabs={DETAIL_TABS}
       activeTab={activeTab}
       onTabClick={(tabKey) => {
frontend/src/pages/ClubDetailPage/components/MobileHeader/MobileHeader.tsx (1)

35-35: 알림 구독 상태의 초기값 동기화가 필요할 수 있습니다.

컴포넌트가 마운트될 때 isNotificationActive가 항상 false로 시작합니다. 이미 구독한 사용자가 페이지를 다시 방문하면 버튼이 비활성 상태로 표시됩니다. 네이티브 앱에서 초기 구독 상태를 받아오는 메시지 핸들러 추가를 고려하세요.

frontend/src/utils/webviewBridge.ts (1)

33-38: postMessage 호출 후 성공 여부를 확인할 수 없습니다.

ReactNativeWebView?.postMessageundefined일 경우 아무 일도 일어나지 않지만 true를 반환합니다. isInAppWebView()true를 반환했는데 ReactNativeWebView가 없는 경우 문제를 감지할 수 없습니다.

♻️ 명시적 검사 제안
 try {
+  if (!window.ReactNativeWebView) {
+    if (isDev) {
+      console.warn('[WebViewBridge] ReactNativeWebView 없음');
+    }
+    return false;
+  }
-  window.ReactNativeWebView?.postMessage(JSON.stringify(message));
+  window.ReactNativeWebView.postMessage(JSON.stringify(message));
   if (isDev) {
     console.log('[WebViewBridge] 앱으로 전송:', message.type);
   }
   return true;
frontend/src/pages/ClubDetailPage/components/MobileHeader/MobileHeader.styles.ts (2)

14-21: 사용되지 않는 $isVisible prop

Header 컴포넌트에 $isVisible prop이 타입으로 선언되어 있지만, 실제 스타일 정의에서 사용되지 않습니다. 불필요한 prop은 제거하거나, 의도된 스타일링이 있다면 구현해 주세요.

♻️ 사용되지 않는 prop 제거 제안
-export const Header = styled.header<{ $isVisible: boolean }>`
+export const Header = styled.header`
   position: relative;
   height: 60px;
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 0 16px;
 `;

36-37: 하드코딩된 rgba 값 중복

IconButtonNotificationButton에서 동일한 rgba(255, 255, 255, 0.8) 값이 중복 사용되고 있습니다. 추후 유지보수를 위해 테마 토큰으로 추출하거나 상수로 정의하는 것을 고려해 주세요.

Also applies to: 59-62

- 스크롤 로직 공통화를 위한 useScrollTo 커스텀 훅 생성
- ScrollToTopButton 및 ClubDetailPage에 새로운 훅 적용
- 상세페이지 탭 전환 시 히스토리가 쌓이지 않도록 replace 옵션 적용하여 뒤로가기 동작 개선
- 원본 탭 클릭 시 스크롤 이동 제거
- MobileHeader 탭바(sticky) 클릭 시에만 컨텐츠로 스크롤
Copy link
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.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx`:
- Around line 92-96: NotificationIcon uses the same color as NotificationButton
when isNotificationActive is true, making the icon invisible; update the fill
logic in ClubDetailTopBar's NotificationIcon usage so when isNotificationActive
is true the icon uses a contrasting color (e.g., theme.colors.white or
theme.colors.primary[50]) instead of theme.colors.primary[900], otherwise keep
theme.colors.gray[500]; reference the NotificationIcon component, the
isNotificationActive flag and the NotificationButton styling to ensure
active-state contrast.
- Line 40: isNotificationActive이 항상 false로 초기화되어 구독된 사용자에게 잘못된 상태를 보여주므로
ClubDetailTopBar 컴포넌트의 마운트 시점에 실제 구독 상태를 받아와 상태를 동기화하세요; 구체적으로
ClubDetailTopBar에서 useEffect를 추가해 webviewBridge의 구독 상태 조회 API(예:
webviewBridge.getNotificationSubscriptionStatus 또는 새로 추가될 유사 함수)를 호출하고 그 결과로
setIsNotificationActive를 호출해 초기값을 설정하거나, 대신 부모 컴포넌트에서 isNotificationActive 초기값을
props로 전달받도록 ClubDetailTopBar의 인터페이스를 변경해 초기 상태를 반영하도록 하세요.
♻️ Duplicate comments (1)
frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx (1)

61-69: 알림 구독 해제 기능이 누락되었습니다.

현재 handleNotificationClick은 구독만 처리하고, 이미 구독된 상태에서 클릭 시 구독 해제 기능이 없습니다.

🧹 Nitpick comments (2)
frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.styles.ts (1)

14-21: $isVisible prop이 사용되지 않고 있습니다.

Header 컴포넌트에서 $isVisible prop을 선언했지만 실제 스타일에서 사용하지 않습니다. 의도된 동작이라면 prop을 제거하고, 스타일 변경이 필요하다면 구현해 주세요.

♻️ 미사용 prop 제거 제안
-export const Header = styled.header<{ $isVisible: boolean }>`
+export const Header = styled.header`
   position: relative;
   height: 60px;
   display: flex;
   align-items: center;
   justify-content: space-between;
   padding: 0 16px;
 `;
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

81-89: 타입 안전성을 위해 tabs 정의와 onTabClick 타입을 일치시키세요.

tabs prop에서 keyTAB_TYPE 상수를 사용하지만, ClubDetailTopBarPropsonTabClickstring을 받습니다. 부모에서 tabKey as TabType 캐스팅이 필요하게 되어 타입 안전성이 떨어집니다.

♻️ 제네릭 또는 명시적 타입 사용 제안

ClubDetailTopBar에서 TabItemonTabClick 타입을 일치시키거나, 부모에서 타입을 명시하세요:

// ClubDetailTopBar.tsx에서
interface ClubDetailTopBarProps<T extends string = string> {
  // ...
  tabs?: { key: T; label: string }[];
  activeTab?: T;
  onTabClick?: (tabKey: T) => void;
}

또는 현재 구조를 유지하면서 TAB_TYPE 값들을 상수로 export하여 ClubDetailTopBar에서도 재사용할 수 있습니다.

Copy link
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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx`:
- Around line 54-57: The handleTabClick currently calls setSearchParams({ tab:
tabKey }, { replace: true }) which wipes existing query params (e.g.,
is_subscribed); change it to use the functional form of setSearchParams inside
handleTabClick (referencing handleTabClick and setSearchParams) so you
merge/update the existing URLSearchParams and only set/replace the tab key while
preserving other params, then call with { replace: true } as before.
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

25-25: 탑바 높이 하드코딩은 오프셋 불일치 리스크가 있습니다.
디자인/세이프에어리어 변경 시 스크롤 오프셋이 어긋날 수 있어, 실제 높이 측정 또는 CSS 변수로 관리하는 방식을 권장합니다.

Also applies to: 46-52

소개내용/활동사진 탭 클릭 시 Sticky 탭바 하단에 콘텐츠가 정확히 위치하도록 높이 값을 65에서 50으로 조정
웹 모바일 환경에서 링크 등을 통해 상세 페이지로 직접 접근했을 때,
이전 히스토리가 없으면 메인 페이지로 이동하도록 뒤로가기 로직을 개선
Comment on lines +1 to +2
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />
Copy link
Member Author

Choose a reason for hiding this comment

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

TypeScript가 *.svg?react import를 인식하기 위한 타입 정의 파일입니다.
없으면 svg 컴포넌트 import 시 TS2307 에러 발생합니다.

Comment on lines +25 to +43
export const postMessageToApp = (message: WebViewMessage): boolean => {
if (!isInAppWebView()) {
if (isDev) {
console.log('[WebViewBridge] 웹 환경, 메시지 무시:', message.type);
}
return false;
}

try {
window.ReactNativeWebView?.postMessage(JSON.stringify(message));
if (isDev) {
console.log('[WebViewBridge] 앱으로 전송:', message.type);
}
return true;
} catch (error) {
console.error('[WebViewBridge] 전송 실패:', error);
return false;
}
};
Copy link
Member Author

Choose a reason for hiding this comment

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

[동작 흐름]

  1. 웹뷰에서 requestNavigateBack() 등 호출
  2. postMessageToApp()이 isInAppWebView()로 앱 환경인지 체크
  3. 앱이면 window.ReactNativeWebView.postMessage()로 JSON 전송
  4. React Native 앱이 메시지 수신 → 네이티브 동작 실행 (뒤로가기, 알림 등)

좀 더 구체적인 원리는 노션 참고

https://www.notion.so/WebView-Bridge-2efaad23209680c79da0e58abcb4f191?source=copy_link

Copy link
Member

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 문서를 잘 작성해주셔서 이해하기 쉬웠습니다

payload를 강제해야 하는 부분이 신기하기도 하고 그에 따른 Facade패턴이나 타입 정의도 흥미롭게 봤습니다 !

Copy link
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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx`:
- Around line 55-66: handleBackClick currently reads the internal React Router
field window.history.state.idx (not public); replace this with a supported check
by either using window.history.length to determine if a back navigation is
possible or by reading router state via useLocation() (e.g., check
location.state) together with navigate and requestNavigateBack; update the logic
inside handleBackClick (and keep calling requestNavigateBack and navigate as
before) so it no longer references window.history.state.idx and instead uses the
chosen public API to decide between navigate(-1) and navigate('/', { replace:
true }).
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

40-41: 사용하지 않는 변수가 있습니다.

isLaptopisDesktop이 구조 분해되었지만 사용되지 않습니다. 필요한 값만 추출하면 코드가 더 명확해집니다.

♻️ 제안 수정안
-  const { isMobile, isTablet, isLaptop, isDesktop } = useDevice();
+  const { isMobile, isTablet } = useDevice();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments