Skip to content

Comments

[release] FE v1.1.25#1220

Merged
seongwon030 merged 27 commits intomainfrom
develop-fe
Feb 21, 2026
Merged

[release] FE v1.1.25#1220
seongwon030 merged 27 commits intomainfrom
develop-fe

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Feb 21, 2026

#️⃣연관된 이슈

ex) #이슈번호, #이슈번호

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지/동영상 첨부 가능)

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • 카카오톡 앱에서 클럽 상세 페이지 접근 시 앱으로 열기 버튼 추가
    • 카카오톡 앱 오픈 시 로딩 상태 표시
  • 테스트

    • 모집 마감일 표시 로직에 대한 테스트 확대

suhyun113 and others added 27 commits February 11, 2026 00:16
[fix] 레이아웃이 깨지는 버그 수정
…-636

지원하기 버튼 테스트 수정
- Android: intent URL 스킴으로 앱 실행 (미설치 시 Play Store 이동)
- iOS: 카카오톡 외부 브라우저로 열어 Universal Link 트리거
- 앱 웹뷰 / 카카오톡 / 일반 브라우저 3단계 조건 분기 적용
- 카카오톡 환경에서만 앱열기 버튼 노출
- intent URL 스킴 대신 kakaotalk://web/openExternal로 외부 브라우저 열기
- Android App Links(assetlinks.json) 기반으로 바로 앱 실행되도록 개선
- iOS/Android 동일한 로직으로 단순화
- intent URL에 S.browser_fallback_url 파라미터 추가
- 앱 설치 시 바로 실행, 미설치 시 Play Store로 자동 리다이렉트
- iOS는 Safari 외부 브라우저 방식 유지
- iOS에서도 프로덕션 도메인(www.moadong.com) URL을 사용하도록 buildProductionUrl 추가
- kakaotalk://web/openExternal 실패 시 1.5초 후 App Store로 자동 이동
- ANDROID_HOST를 APP_HOST로 통합하여 양 플랫폼 공통 사용
- Safari에서 프로덕션 URL 대신 App Store 링크를 직접 열도록 수정
- 설치됨 → App Store에서 "열기", 미설치 → "받기"로 양쪽 케이스 대응
- 동작 불가능했던 document.hidden 기반 타임아웃 폴백 로직 제거
- Safari에서 프로덕션 URL 열기로 앱 설치 시 바로 실행되도록 변경
- 미설치 시 Smart App Banner를 통해 App Store 유도
- moadongapp:// 커스텀 스킴으로 앱 직접 실행 시도
- 2초 후에도 페이지가 visible이면 앱 미설치로 판단 → App Store 이동
- 앱이 열려 페이지가 hidden 되면 타이머 취소
- 상대경로 제외
- useRef로 타이머를 추적해 중복 호출 시 이전 타이머 취소
- useEffect 정리 함수로 컴포넌트 언마운트 시 잔여 타이머 정리
Co-authored-by: Cursor <cursoragent@cursor.com>
…ink-fix-MOA-665

[Feature] 카카오톡 링크 내 유니버셜 링크/앱링크 미동작 대응
@seongwon030 seongwon030 self-assigned this Feb 21, 2026
@vercel
Copy link

vercel bot commented Feb 21, 2026

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

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Feb 21, 2026 5:43am

@seongwon030 seongwon030 added the 💻 FE Frontend label Feb 21, 2026
@seongwon030 seongwon030 added 📈 release 릴리즈 배포 ✨ Feature 기능 개발 labels Feb 21, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

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

카카오톡 내부 브라우저에서 앱을 열기 위한 기능을 추가합니다. 딥링크 감지, 플랫폼별 URL 생성, 앱 미설치 시 스토어 폴백을 포함한 새 훅을 도입하고, ClubDetailTopBar UI를 업데이트하여 로딩 상태와 버튼을 추가합니다. 추가로 테스트 데이터와 래퍼 구성을 조정합니다.

Changes

Cohort / File(s) Summary
카카오톡 앱 열기 기능
frontend/src/hooks/useOpenAppFromKakao.ts, frontend/src/utils/isKakaoTalkBrowser.ts
새 훅과 유틸리티를 추가하여 카카오톡 브라우저 감지 및 딥링크/스토어 폴백을 통한 앱 열기 기능 구현
ClubDetailTopBar 통합
frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx, frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.styles.ts
카카오톡 환경에서 앱 열기 버튼과 로딩 오버레이를 렌더링하는 UI 추가 및 스타일 컴포넌트 정의
테스트 및 구성 업데이트
frontend/src/utils/getDeadLineText.test.ts, frontend/src/App.tsx
마감 텍스트 테스트 케이스 확장 및 GlobalStyles/GlobalBoundary 래퍼 순서 조정

Sequence Diagram(s)

sequenceDiagram
    actor User as 사용자
    participant ClubDetailTopBar
    participant useOpenAppFromKakao
    participant isKakaoTalkBrowser
    participant KakaoIntent as Kakao Deep Link
    participant AppStore as App Store

    User->>ClubDetailTopBar: 앱 열기 버튼 클릭
    ClubDetailTopBar->>isKakaoTalkBrowser: 카카오톡 브라우저 확인
    isKakaoTalkBrowser-->>ClubDetailTopBar: true/false
    
    rect rgba(100, 150, 200, 0.5)
    ClubDetailTopBar->>useOpenAppFromKakao: openApp() 호출
    useOpenAppFromKakao->>useOpenAppFromKakao: 플랫폼 감지 (Android/iOS)
    useOpenAppFromKakao->>useOpenAppFromKakao: 딥링크 URL 구성
    useOpenAppFromKakao->>KakaoIntent: 카카오 intent 또는 custom scheme 전달
    end
    
    alt 앱이 2초 내에 열림
        KakaoIntent-->>useOpenAppFromKakao: 앱 열기 성공
        useOpenAppFromKakao->>useOpenAppFromKakao: 타이머 정리, isLoading = false
    else 앱이 설치되지 않음
        useOpenAppFromKakao->>AppStore: 2초 후 App Store로 리다이렉트
        AppStore-->>User: 스토어 페이지 표시
    end
    
    useOpenAppFromKakao-->>ClubDetailTopBar: { openApp, isLoading }
    ClubDetailTopBar-->>User: 로딩 상태 표시 업데이트
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

💻 FE, 📈 release

Suggested reviewers

  • lepitaaar
  • suhyun113
  • oesnuj
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive Pull request title '[release] FE v1.1.25'는 버전 릴리스를 나타내지만, 구체적인 변경 내용을 명확히 전달하지 못합니다. 제목을 더 구체적으로 수정하여 주요 변경 사항을 반영하세요. 예: '[release] FE v1.1.25: Add KakaoTalk app opening support' 또는 유사한 형태로 작성하시기 바랍니다.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop-fe

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 removed the ✨ Feature 기능 개발 label Feb 21, 2026
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.

수고하셨습니다 릴리즈 태워보내죠🚀

@seongwon030 seongwon030 merged commit fbce38f into main Feb 21, 2026
3 of 4 checks passed
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

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

4-16: 기존 TopBarWrapper의 하드코딩된 z-index 값 정리 (선택)

LoadingOverlayZ_INDEX.overlay 상수를 사용하지만, TopBarWrapperz-index: 100으로 하드코딩되어 있습니다. Z_INDEX 상수(Z_INDEX.header = 1000)로 통일하면 프로젝트 전체에서 스태킹 컨텍스트를 한 곳에서 관리할 수 있습니다.

♻️ 선택적 개선 제안
 export const TopBarWrapper = styled.div<{ $isVisible: boolean }>`
   position: fixed;
   top: 0;
   left: 0;
   right: 0;
-  z-index: 100;
+  z-index: ${Z_INDEX.header};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.styles.ts`
around lines 4 - 16, Replace the hardcoded z-index in TopBarWrapper with the
shared Z_INDEX constant to centralize stacking order management; update the
styled component TopBarWrapper to use Z_INDEX.header (the same constant used by
LoadingOverlay) instead of 100 so the header stacking context is controlled from
the Z_INDEX enum/object.
frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx (1)

133-135: onClick 래퍼 화살표 함수 제거 (선택)

onClick={() => openApp()}은 매 렌더링마다 새로운 함수를 생성합니다. 직접 참조를 전달하면 충분합니다.

♻️ 선택적 개선 제안
-            <Styled.AppOpenButton onClick={() => openApp()}>
+            <Styled.AppOpenButton onClick={openApp}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx`
around lines 133 - 135, The button's onClick currently creates a new lambda each
render via onClick={() => openApp()}; change it to pass the handler directly—use
onClick={openApp} on Styled.AppOpenButton (or bind appropriately if openApp
requires arguments or a specific this) to avoid unnecessary function recreation
and improve render performance.
frontend/src/utils/getDeadLineText.test.ts (1)

97-107: describe 블록 외부에 위치한 테스트 케이스

Lines 97–107의 두 it() 블록이 describe('getDeadlineText 함수 테스트', () => {...}) 외부에 선언되어 있습니다. 기능적으로는 동작하지만, 테스트 리포트에서 그룹화가 깨지고 파일 내 일관성이 떨어집니다.

♻️ 제안: describe 블록 내부로 이동
-});
-
-it('CLOSED 상태이고 종료일이 있으면 지난 모집 날짜를 표시한다', () => {
-  expect(getDeadlineText(null, new Date('2024-12-31'), 'CLOSED')).toBe(
-    '지난 모집・2024 12/31',
-  );
-});
-
-it('UPCOMING 상태이고 시작일이 있으면 모집 시작일을 표시한다', () => {
-  expect(
-    getDeadlineText(new Date('2024-11-15T14:30:00'), null, 'UPCOMING'),
-  ).toBe('11월 15일 14:30 모집 시작');
-});
+
+  it('CLOSED 상태이고 종료일이 있으면 지난 모집 날짜를 표시한다', () => {
+    expect(getDeadlineText(null, new Date('2024-12-31'), 'CLOSED')).toBe(
+      '지난 모집・2024 12/31',
+    );
+  });
+
+  it('UPCOMING 상태이고 시작일이 있으면 모집 시작일을 표시한다', () => {
+    expect(
+      getDeadlineText(new Date('2024-11-15T14:30:00'), null, 'UPCOMING'),
+    ).toBe('11월 15일 14:30 모집 시작');
+  });
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/getDeadLineText.test.ts` around lines 97 - 107, The two
standalone it() tests for getDeadlineText should be moved inside the existing
describe('getDeadlineText 함수 테스트', () => { ... }) block so they are grouped with
the other specs; locate the two it(...) blocks that assert CLOSED and UPCOMING
behaviors and cut-and-paste them into the describe block (keeping their contents
unchanged) so test reports are consistently grouped under getDeadlineText and
file structure remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/hooks/useOpenAppFromKakao.ts`:
- Around line 12-16: The visibilitychange listener added inside openApp is never
removed, causing leaked/accumulating listeners; fix by tracking the listener
callback in a ref (e.g., visibilityHandlerRef) and always call
document.removeEventListener('visibilitychange', visibilityHandlerRef.current)
when clearing timer or in the useEffect cleanup, and also remove any existing
listener before adding a new one in openApp; ensure timerRef and the visibility
handler ref are both cleared/nullified after removal.

---

Nitpick comments:
In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.styles.ts`:
- Around line 4-16: Replace the hardcoded z-index in TopBarWrapper with the
shared Z_INDEX constant to centralize stacking order management; update the
styled component TopBarWrapper to use Z_INDEX.header (the same constant used by
LoadingOverlay) instead of 100 so the header stacking context is controlled from
the Z_INDEX enum/object.

In
`@frontend/src/pages/ClubDetailPage/components/ClubDetailTopBar/ClubDetailTopBar.tsx`:
- Around line 133-135: The button's onClick currently creates a new lambda each
render via onClick={() => openApp()}; change it to pass the handler directly—use
onClick={openApp} on Styled.AppOpenButton (or bind appropriately if openApp
requires arguments or a specific this) to avoid unnecessary function recreation
and improve render performance.

In `@frontend/src/utils/getDeadLineText.test.ts`:
- Around line 97-107: The two standalone it() tests for getDeadlineText should
be moved inside the existing describe('getDeadlineText 함수 테스트', () => { ... })
block so they are grouped with the other specs; locate the two it(...) blocks
that assert CLOSED and UPCOMING behaviors and cut-and-paste them into the
describe block (keeping their contents unchanged) so test reports are
consistently grouped under getDeadlineText and file structure remains
consistent.

Comment on lines +12 to +16
useEffect(() => {
return () => {
if (timerRef.current !== null) clearTimeout(timerRef.current);
};
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

visibilitychange 리스너가 컴포넌트 언마운트 시 제거되지 않아 이벤트 리스너가 누적됩니다

openApp()이 호출될 때마다 documentvisibilitychange 리스너가 추가되지만, useEffect 정리 함수는 타이머만 해제하고 리스너는 제거하지 않습니다.

문제점:

  1. 언마운트 시 누수: visibilitychange가 발생하기 전에 컴포넌트가 언마운트되면, 리스너가 document에 잔류하여 이후 다른 가시성 변경 이벤트에서 오래된 콜백이 실행됩니다.
  2. 리스너 누적: iOS에서 openApp()을 여러 번 호출하면 { once: true } 리스너가 계속 쌓입니다. visibilitychange가 발생하기 전까지는 중복 리스너가 document에 존재합니다.
🐛 제안 수정
 const useOpenAppFromKakao = () => {
   const [isLoading, setIsLoading] = useState(false);
   const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  const visibilityHandlerRef = useRef<(() => void) | null>(null);

   useEffect(() => {
     return () => {
       if (timerRef.current !== null) clearTimeout(timerRef.current);
+      if (visibilityHandlerRef.current !== null) {
+        document.removeEventListener('visibilitychange', visibilityHandlerRef.current);
+        visibilityHandlerRef.current = null;
+      }
     };
   }, []);

   const openApp = () => {
     // ... Android 처리 ...

+    // 이전 리스너 제거 후 새 리스너 등록
+    if (visibilityHandlerRef.current !== null) {
+      document.removeEventListener('visibilitychange', visibilityHandlerRef.current);
+      visibilityHandlerRef.current = null;
+    }
+
     if (timerRef.current !== null) clearTimeout(timerRef.current);
     setIsLoading(true);

     const url = new URL(currentUrl);
     window.location.href = `${IOS_SCHEME}://${url.pathname}${url.search}${url.hash}`;

     timerRef.current = setTimeout(() => {
       timerRef.current = null;
+      visibilityHandlerRef.current = null;
       setIsLoading(false);
       if (!document.hidden) {
         window.location.href = `kakaotalk://web/openExternal?url=${encodeURIComponent(APP_STORE_LINKS.iphone)}`;
       }
     }, 2000);

-    document.addEventListener(
-      'visibilitychange',
-      () => {
-        if (document.hidden && timerRef.current !== null) {
-          clearTimeout(timerRef.current);
-          timerRef.current = null;
-          setIsLoading(false);
-        }
-      },
-      { once: true },
-    );
+    const handleVisibilityChange = () => {
+      if (document.hidden && timerRef.current !== null) {
+        clearTimeout(timerRef.current);
+        timerRef.current = null;
+        visibilityHandlerRef.current = null;
+        setIsLoading(false);
+      }
+    };
+    visibilityHandlerRef.current = handleVisibilityChange;
+    document.addEventListener('visibilitychange', handleVisibilityChange, { once: true });
   };

Also applies to: 48-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/hooks/useOpenAppFromKakao.ts` around lines 12 - 16, The
visibilitychange listener added inside openApp is never removed, causing
leaked/accumulating listeners; fix by tracking the listener callback in a ref
(e.g., visibilityHandlerRef) and always call
document.removeEventListener('visibilitychange', visibilityHandlerRef.current)
when clearing timer or in the useEffect cleanup, and also remove any existing
listener before adding a new one in openApp; ensure timerRef and the visibility
handler ref are both cleared/nullified after removal.

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

Labels

💻 FE Frontend 📈 release 릴리즈 배포

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants