Skip to content

Comments

[feature] 웹뷰 감지 로직 추가 및 웹뷰용 UI 조건부 처리#828

Merged
oesnuj merged 13 commits intodevelop-fefrom
feature/#777-hide-header-webview-MOA-282
Nov 11, 2025
Merged

[feature] 웹뷰 감지 로직 추가 및 웹뷰용 UI 조건부 처리#828
oesnuj merged 13 commits intodevelop-fefrom
feature/#777-hide-header-webview-MOA-282

Conversation

@oesnuj
Copy link
Member

@oesnuj oesnuj commented Nov 10, 2025

#️⃣연관된 이슈

#777

📝작업 내용

1. isInAppWebView 유틸리티 함수 구현 및 테스트 코드 추가

  • MoadongApp 커스텀 userAgent 감지 로직 추가
  • React Native 웹뷰 환경 판별 기능
const isInAppWebView = () => /MoadongApp/.test(navigator.userAgent);

export default isInAppWebView;

2. 클럽 상세페이지 웹뷰 최적화

  • BackNavigationBar: 웹뷰 환경에서 비노출 처리
  • InfoTabs: 웹뷰일 때 sticky top 위치를 0px로 조정하여 빈 공간 제거
  • 웹뷰 환경에 맞춘 레이아웃 최적화로 사용자 경험 개선

3. 소개페이지, 총동연 소개 페이지 웹뷰 대응

  • Header 컴포넌트 웹뷰 환경에서 비노출 처리

Summary by CodeRabbit

업데이트 내용

  • 새로운 기능

    • 앱 내 웹뷰에서 상단 네비게이션(뒤로가기/헤더)을 자동으로 숨겨 화면을 간결하게 표시
  • 스타일

    • 앱 웹뷰 환경에 따라 콘텐츠 상단 위치를 조정해 레이아웃 일관성 개선
  • 테스트

    • 웹뷰 감지 동작에 대한 단위 테스트 추가로 안정성 향상

@vercel
Copy link

vercel bot commented Nov 10, 2025

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

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Nov 11, 2025 11:59am

@oesnuj oesnuj added ✨ Feature 기능 개발 💻 FE Frontend labels Nov 10, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

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

앱 내 웹뷰 여부를 감지하는 isInAppWebView 유틸을 추가하고, 이를 사용해 여러 페이지에서 상단 Header 및 BackNavigationBar 렌더링과 InfoTabs의 top 오프셋을 웹뷰 환경일 때 숨기거나 0px으로 조정하도록 변경했습니다.

Changes

Cohort / File(s) 변경 요약
웹뷰 감지 유틸
frontend/src/utils/isInAppWebView.ts,
frontend/src/utils/isInAppWebView.test.ts
navigator.userAgent에서 MoadongApp 토큰을 검사하는 isInAppWebView() 함수 추가 및 해당 함수 단위테스트 추가
ClubDetailPage — 네비게이션 숨김
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
isInAppWebView 임포트 후 앱 웹뷰일 때 BackNavigationBar를 렌더링하지 않도록 조건부 처리 (RecommendedClubs는 주석 상태 유지)
InfoTabs — 스타일 프로퍼티 추가
frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts,
frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx
InfoTabWrapperisInAppWebView?: boolean prop 추가, 웹뷰일 땐 top: 0px 아니면 top: 70px 적용; InfoTabs에서 isInAppWebView() 결과를 전달
페이지 레벨 Header 조건부 렌더링
frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx,
frontend/src/pages/IntroducePage/IntroducePage.tsx
isInAppWebView()를 사용해 앱 웹뷰 환경이면 Header를 렌더링하지 않도록 변경

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant isInApp as isInAppWebView()
    participant Page as Page Component
    participant Header as Header / BackNavigationBar
    participant InfoTabs as InfoTabs / InfoTabWrapper

    Browser->>isInApp: 요청 (navigator.userAgent)
    isInApp-->>Page: true / false

    alt isInApp == true
        Page--xHeader: Header / BackNavigationBar 렌더링 생략
        Page->>InfoTabs: <isInAppWebView: true>
        InfoTabs->>InfoTabs: top = 0px
    else isInApp == false
        Page->>Header: Header / BackNavigationBar 렌더링
        Page->>InfoTabs: <isInAppWebView: false>
        InfoTabs->>InfoTabs: top = 70px
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 분

  • 주의 필요 영역:
    • isInAppWebView()의 userAgent 매칭이 여러 앱 버전/케이스(대소문자, 접두/접미 토큰 등)에서 정확히 동작하는지 확인
    • InfoTabs의 top 변경으로 인한 레이아웃/오버랩 문제(특히 모바일 및 다양한 뷰포트) 검증
    • ClubDetailPage에서 RecommendedClubs 주석 상태가 의도된 것인지 확인

Possibly related issues

  • MOA-282: 웹뷰용 상세페이지 상단 헤더 비노출 처리 — 이 PR은 웹뷰 감지 및 상세페이지/관련 페이지에서 헤더를 숨기는 목적과 일치합니다.

Possibly related PRs

  • PR #819 — ClubDetailPage 관련 동일 파일을 변경하는 PR로 RecommendedClubs import/usage에 코드 레벨 충돌 가능성 있음
  • PR #813 — ClubDetailPage에서 RecommendedClubs 처리를 수정한 PR로 중복/상충 변경 가능성 있음
  • PR #663 — ClubUnionPage의 Header 처리/통합과 관련된 PR로 Header 조건부 렌더링 변경과 직접적 관계 있음

Suggested reviewers

  • seongwon030
  • lepitaaar
  • suhyun113

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 웹뷰 감지 로직 추가와 웹뷰용 UI 조건부 처리라는 변경 사항의 핵심을 명확하게 설명하고 있습니다.
Linked Issues check ✅ Passed PR은 연관 이슈 MOA-282의 요구사항인 웹뷰 환경에서 헤더 비노출 처리를 구현했으며, isInAppWebView 유틸리티를 통해 웹뷰 감지 로직을 추가했습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항은 웹뷰 감지 및 UI 조건부 처리라는 명확한 범위 내에서 이루어졌으며, 무관한 변경사항은 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#777-hide-header-webview-MOA-282

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

🧹 Nitpick comments (2)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

17-17: 중복 훅 호출을 제거하고 props로 전달하세요.

useIsWebView 훅이 ClubDetailPageInfoTabs 컴포넌트에서 각각 호출되고 있습니다. 같은 값을 중복 계산하는 것보다, 부모 컴포넌트에서 한 번만 호출하고 자식에게 props로 전달하는 것이 더 효율적입니다.

Line 61의 InfoTabs 컴포넌트에 isWebView를 prop으로 전달하도록 수정하세요:

-        <InfoTabs onTabClick={scrollToSection} />
+        <InfoTabs onTabClick={scrollToSection} isWebView={isWebView} />

그리고 InfoTabs.tsx에서 훅 호출을 제거하고 props를 받도록 수정하세요 (해당 파일의 별도 코멘트 참조).

Also applies to: 23-23

frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (1)

5-5: 중복 훅 호출을 제거하고 props로 받으세요.

useIsWebView 훅을 이 컴포넌트와 부모 컴포넌트(ClubDetailPage) 양쪽에서 호출하고 있습니다. 부모에서 계산한 값을 props로 받는 것이 더 효율적입니다.

다음 diff를 적용하여 props로 받도록 수정하세요:

 import { useState } from 'react';
 import * as Styled from './InfoTabs.styles';
 import useMixpanelTrack from '@/hooks/useMixpanelTrack';
 import { EVENT_NAME } from '@/constants/eventName';
-import useIsWebView from '@/hooks/useIsWebview';

 const tabLabels = ['모집정보', '동아리정보', '소개글', '활동사진'];

-const InfoTabs = ({ onTabClick }: { onTabClick: (index: number) => void }) => {
+const InfoTabs = ({ 
+  onTabClick,
+  isWebView 
+}: { 
+  onTabClick: (index: number) => void;
+  isWebView: boolean;
+}) => {
   const [activeTab, setActiveTab] = useState(0);
   const trackEvent = useMixpanelTrack();
-  const { isWebView } = useIsWebView();

Also applies to: 12-12

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dda2c8e and f1ce613.

📒 Files selected for processing (4)
  • frontend/src/hooks/useIsWebview.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/hooks/useIsWebview.ts
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
frontend/**/*.tsx

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🧠 Learnings (4)
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.

Applied to files:

  • frontend/src/hooks/useIsWebview.ts
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🔇 Additional comments (4)
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

49-49: LGTM!

WebView 환경에서 BackNavigationBar를 숨기는 로직이 PR 목적에 부합하며 올바르게 구현되었습니다.

frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (1)

25-25: LGTM!

isWebView prop을 styled component에 올바르게 전달하고 있습니다.

frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts (1)

3-3: LGTM!

isWebView optional prop의 타입 정의가 올바릅니다.

frontend/src/hooks/useIsWebview.ts (1)

1-18: 성능 최적화: 결과값을 메모이제이션하세요.

userAgent 체크는 모든 렌더링마다 재계산되고 있습니다. 동일한 환경에서는 결과가 변하지 않으므로 useMemo를 사용하여 최적화해야 합니다. 또한 SSR 환경에서 navigator가 정의되지 않을 수 있으므로 안전성 체크도 필요합니다.

다음 diff를 적용하여 개선하세요:

+import { useMemo } from 'react';
+
 const useIsWebView = () => {
-  const userAgent = navigator.userAgent;
-
-  // Android WebView
-  const isAndroidWebView = /Android/.test(userAgent) && /wv/.test(userAgent);
-
-  // iOS WebView
-  const isIOSWebView =
-    /iPhone|iPad|iPod/.test(userAgent) &&
-    /AppleWebKit/.test(userAgent) &&
-    !/Safari/.test(userAgent);
-
-  return {
-    isWebView: isAndroidWebView || isIOSWebView,
-    isIOSWebView,
-    isAndroidWebView,
-  };
+  return useMemo(() => {
+    if (typeof navigator === 'undefined') {
+      return {
+        isWebView: false,
+        isIOSWebView: false,
+        isAndroidWebView: false,
+      };
+    }
+
+    const userAgent = navigator.userAgent;
+    
+    // Android WebView
+    const isAndroidWebView = /Android/.test(userAgent) && /wv/.test(userAgent);
+    
+    // iOS WebView
+    const isIOSWebView =
+      /iPhone|iPad|iPod/.test(userAgent) &&
+      /AppleWebKit/.test(userAgent) &&
+      !/Safari/.test(userAgent);
+    
+    return {
+      isWebView: isAndroidWebView || isIOSWebView,
+      isIOSWebView,
+      isAndroidWebView,
+    };
+  }, []);
 };
⛔ Skipped due to learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 541
File: frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx:41-43
Timestamp: 2025-07-20T11:48:50.207Z
Learning: moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA이므로, window 객체 사용에 대한 SSR 호환성 문제를 제기하지 않아야 합니다.

display: none;
position: sticky;
top: 70px;
top: ${({ isWebView }) => (isWebView ? '0px' : '70px')};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

매직 넘버를 명명된 상수로 교체하세요.

0px70px 값을 직접 사용하는 대신 명명된 상수로 교체하여 의미를 명확히 하세요.

As per coding guidelines.

파일 상단에 상수를 정의하고 사용하도록 수정하세요:

 import styled from 'styled-components';

+const TAB_TOP_OFFSET_WITH_HEADER = '70px';
+const TAB_TOP_OFFSET_WEBVIEW = '0px';
+
 export const InfoTabWrapper = styled.div<{ isWebView?: boolean }>`
   display: none;
   position: sticky;
-  top: ${({ isWebView }) => (isWebView ? '0px' : '70px')};
+  top: ${({ isWebView }) => (isWebView ? TAB_TOP_OFFSET_WEBVIEW : TAB_TOP_OFFSET_WITH_HEADER)};
   z-index: 1;
📝 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.

Suggested change
top: ${({ isWebView }) => (isWebView ? '0px' : '70px')};
import styled from 'styled-components';
const TAB_TOP_OFFSET_WITH_HEADER = '70px';
const TAB_TOP_OFFSET_WEBVIEW = '0px';
export const InfoTabWrapper = styled.div<{ isWebView?: boolean }>`
display: none;
position: sticky;
top: ${({ isWebView }) => (isWebView ? TAB_TOP_OFFSET_WEBVIEW : TAB_TOP_OFFSET_WITH_HEADER)};
z-index: 1;
🤖 Prompt for AI Agents
In frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
around line 6, the inline magic numbers '0px' and '70px' should be replaced with
named constants: define meaningful constants at the top of the file (e.g.,
WEBVIEW_TOP_OFFSET and DEFAULT_TOP_OFFSET) and use them in the styled component
expression instead of the literal strings to clarify intent and follow coding
guidelines.

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

♻️ Duplicate comments (1)
frontend/src/hooks/useIsWebview.ts (1)

11-14: 이전 리뷰에서 지적된 iOS WebView 감지 로직 미개선

이 iOS WebView 감지 로직은 이전 리뷰에서 이미 신뢰할 수 없는 방식으로 지적되었습니다. !/Safari/.test(userAgent) 검사는 최신 WKWebView에서 false negative를 발생시킬 수 있습니다.

이전 리뷰의 권장사항을 적용해주세요:

  • 앱 수준에서 customUserAgent 설정 또는 커스텀 HTTP 헤더 사용
  • 명시적인 토큰 감지 추가 (예: /\bWKWebView\b/i 또는 앱별 토큰)
🧹 Nitpick comments (1)
frontend/src/hooks/useIsWebview.ts (1)

1-33: 정규식 패턴을 named constants로 추출하세요

코딩 가이드라인에 따라 인라인 정규식을 명명된 상수로 추출하면 가독성과 유지보수성이 향상됩니다.

Based on coding guidelines

다음과 같이 상수를 파일 상단에 정의하세요:

// WebView 감지용 정규식 패턴
const REACT_NATIVE_WEBVIEW_PATTERN = /MoadongApp/;
const ANDROID_WEBVIEW_PATTERN = /wv/;
const ANDROID_PLATFORM_PATTERN = /Android/;
const IOS_DEVICE_PATTERN = /iPhone|iPad|iPod/;
const APPLE_WEBKIT_PATTERN = /AppleWebKit/;
const SAFARI_PATTERN = /Safari/;

const useIsWebView = () => {
  const result = useMemo(() => {
    const userAgent = navigator.userAgent;

    // React Native WebView (MoadongApp 커스텀)
    const isReactNativeWebView = REACT_NATIVE_WEBVIEW_PATTERN.test(userAgent);

    // Android Native WebView
    const isAndroidWebView = 
      ANDROID_PLATFORM_PATTERN.test(userAgent) && 
      ANDROID_WEBVIEW_PATTERN.test(userAgent);

    // iOS Native WebView
    const isIOSWebView =
      IOS_DEVICE_PATTERN.test(userAgent) &&
      APPLE_WEBKIT_PATTERN.test(userAgent) &&
      !SAFARI_PATTERN.test(userAgent);

    const isWebView = isReactNativeWebView || isAndroidWebView || isIOSWebView;

    return {
      isWebView,
      isReactNativeWebView,
      isAndroidWebView,
      isIOSWebView,
    };
  }, []);

  // ... rest of the code
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 109df04 and 3b3c392.

📒 Files selected for processing (1)
  • frontend/src/hooks/useIsWebview.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/hooks/useIsWebview.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.

Applied to files:

  • frontend/src/hooks/useIsWebview.ts

@oesnuj oesnuj changed the title [feature] 웹뷰 감지 훅 추가 및 상세페이지 UI 조정 [feature] 웹뷰 감지 훅 추가 및 웹뷰용 UI 조건부 처리 Nov 10, 2025
Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

user-agent를이용해 모바일에서 다른 화면 보여주는거 신기하네요!

@oesnuj oesnuj changed the title [feature] 웹뷰 감지 훅 추가 및 웹뷰용 UI 조건부 처리 [feature] 웹뷰 감지 로직 추가 및 웹뷰용 UI 조건부 처리 Nov 11, 2025
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: 0

♻️ Duplicate comments (1)
frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts (1)

3-6: 매직 넘버를 명명된 상수로 교체하세요.

이전 리뷰에서 이미 지적된 것처럼, '0px''70px' 값을 명명된 상수로 교체하여 의미를 명확히 하세요.

파일 상단에 상수를 정의하고 사용하세요:

 import styled from 'styled-components';

+const TAB_TOP_OFFSET_WITH_HEADER = '70px';
+const TAB_TOP_OFFSET_WEBVIEW = '0px';
+
-export const InfoTabWrapper = styled.div<{ isInAppWebView?: boolean }>`
+export const InfoTabWrapper = styled.div<{ isInAppWebView?: boolean }>`
   display: none;
   position: sticky;
-  top: ${({ isInAppWebView }) => (isInAppWebView ? '0px' : '70px')};
+  top: ${({ isInAppWebView }) => (isInAppWebView ? TAB_TOP_OFFSET_WEBVIEW : TAB_TOP_OFFSET_WITH_HEADER)};
   z-index: 1;

As per coding guidelines.

🧹 Nitpick comments (2)
frontend/src/pages/IntroducePage/IntroducePage.tsx (1)

11-17: 조건부 헤더 렌더링 패턴의 중복을 고려하세요.

IntroducePage, ClubUnionPage에서 동일한 조건부 헤더 렌더링 패턴({!isInAppWebView() && <Header />})이 반복됩니다. 현재 구현은 명확하고 간단하지만, 향후 로직이 복잡해질 경우를 대비해 재사용 가능한 ConditionalHeader 컴포넌트로 추출하는 것을 고려해보세요.

예시:

// components/common/ConditionalHeader/ConditionalHeader.tsx
import Header from '@/components/common/Header/Header';
import isInAppWebView from '@/utils/isInAppWebView';

const ConditionalHeader = () => {
  if (isInAppWebView()) return null;
  return <Header />;
};

export default ConditionalHeader;

사용:

-import Header from '@/components/common/Header/Header';
+import ConditionalHeader from '@/components/common/ConditionalHeader/ConditionalHeader';
-import isInAppWebView from '@/utils/isInAppWebView';

 const IntroducePage = () => {
   return (
     <>
       <Styled.IntroducePageHeader>
-        {!isInAppWebView() && <Header />}
+        <ConditionalHeader />
       </Styled.IntroducePageHeader>
frontend/src/utils/isInAppWebView.ts (1)

1-1: 함수명이 더 구체적이면 좋겠습니다.

함수명이 isInAppWebView로 되어 있지만, 실제로는 'MoadongApp' 특정 UserAgent를 검사합니다. 향후 다른 인앱 웹뷰 환경을 지원할 가능성이 있다면, isInMoadongApp 또는 isMoadongAppWebView 같이 더 구체적인 이름을 사용하는 것이 명확할 수 있습니다.

예시:

-const isInAppWebView = () => /MoadongApp/.test(navigator.userAgent);
+const isMoadongAppWebView = () => /MoadongApp/.test(navigator.userAgent);

-export default isInAppWebView;
+export default isMoadongAppWebView;

또는 현재 이름을 유지하되, 주석을 추가하여 의도를 명확히 할 수 있습니다:

+/**
+ * MoadongApp의 커스텀 UserAgent를 감지하여 React Native 웹뷰 환경인지 확인합니다.
+ */
 const isInAppWebView = () => /MoadongApp/.test(navigator.userAgent);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b3c392 and d76aa33.

📒 Files selected for processing (6)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx (2 hunks)
  • frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx (1 hunks)
  • frontend/src/pages/IntroducePage/IntroducePage.tsx (1 hunks)
  • frontend/src/utils/isInAppWebView.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.tsx
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/utils/isInAppWebView.ts
  • frontend/src/pages/IntroducePage/IntroducePage.tsx
  • frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx
  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
frontend/**/*.tsx

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.

Files:

  • frontend/src/pages/IntroducePage/IntroducePage.tsx
  • frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx
🧠 Learnings (5)
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.

Applied to files:

  • frontend/src/pages/IntroducePage/IntroducePage.tsx
  • frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Define constants near related logic or ensure names link them clearly.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/InfoTabs/InfoTabs.styles.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: run
🔇 Additional comments (2)
frontend/src/pages/ClubUnionPage/ClubUnionPage.tsx (1)

7-14: 일관된 구현입니다.

IntroducePage와 동일한 패턴으로 구현되어 일관성이 유지되고 있습니다. 앞서 언급한 것처럼, 향후 이 패턴이 더 많은 페이지에서 반복될 경우 재사용 가능한 컴포넌트로 추출하는 것을 고려하세요.

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

1-1: 이 리뷰 의견은 프로젝트 아키텍처에 맞지 않습니다.

retrieved learnings에 따르면, moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA입니다. SSR 환경을 고려할 필요가 없으므로, navigator 객체 접근 안전성 관련 지적은 타당하지 않습니다.

isInAppWebView() 함수는 현재 상태 그대로 문제없습니다.

Likely an incorrect or invalid review comment.

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: 0

🧹 Nitpick comments (2)
frontend/src/utils/isInAppWebView.test.ts (2)

6-13: 속성 디스크립터 일관성 개선을 고려하세요.

afterEach에서 writable: false로 설정하고 있지만, 테스트 케이스(17-20줄, 31-34줄)에서는 writable 속성을 명시하지 않고 있습니다. 원래 userAgent의 속성 디스크립터와 일치하지 않을 수 있습니다.

다음과 같이 개선할 수 있습니다:

  afterEach(() => {
-    // 각 테스트 후 userAgent 원복
     Object.defineProperty(navigator, 'userAgent', {
       value: originalUserAgent,
-      writable: false,
       configurable: true,
     });
   });

그리고 테스트 케이스에서도 일관되게 writable을 명시하지 않는 것이 좋습니다.


15-41: 엣지 케이스 테스트 추가를 고려하세요.

현재 테스트는 기본적인 양성/음성 케이스를 잘 다루고 있지만, 다음과 같은 엣지 케이스 테스트를 추가하면 더욱 견고해질 수 있습니다:

  • 대소문자 구분 테스트 ("moadongapp" vs "MoadongApp")
  • 부분 문자열 매칭 ("MoadongAppV2" 같은 변형)
  • 빈 문자열이나 특수 문자가 포함된 경우

예시:

test('userAgent에 "MoadongApp"의 대소문자 변형이 있을 때', () => {
  Object.defineProperty(navigator, 'userAgent', {
    value: 'Mozilla/5.0 moadongapp',
    configurable: true,
  });
  
  const result = isInAppWebView();
  
  // 구현체에 따라 true 또는 false 예상
  expect(result).toBe(false); // 대소문자 구분 시
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d76aa33 and ba0f3fa.

📒 Files selected for processing (1)
  • frontend/src/utils/isInAppWebView.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.

Files:

  • frontend/src/utils/isInAppWebView.test.ts
🔇 Additional comments (1)
frontend/src/utils/isInAppWebView.test.ts (1)

1-42: 전체적으로 잘 작성된 테스트입니다.

테스트 구조가 명확하고, Given-When-Then 주석을 통해 각 테스트의 의도를 잘 표현하고 있습니다. afterEach를 통한 정리 로직도 적절하게 구현되어 있으며, 핵심 기능을 충분히 검증하고 있습니다.

@oesnuj oesnuj merged commit e3fda4d into develop-fe Nov 11, 2025
4 of 5 checks passed
@oesnuj oesnuj deleted the feature/#777-hide-header-webview-MOA-282 branch January 25, 2026 07:53
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