Skip to content

[release] FE v1.1.15 팝업 및 앱스토어링크 관련 버그 해결#1047

Merged
seongwon030 merged 30 commits intomainfrom
develop-fe
Jan 15, 2026
Merged

[release] FE v1.1.15 팝업 및 앱스토어링크 관련 버그 해결#1047
seongwon030 merged 30 commits intomainfrom
develop-fe

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Jan 15, 2026

#️⃣연관된 이슈

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

📝작업 내용

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

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 모달 렌더링 시스템 개선으로 더 안정적인 사용자 경험 제공
    • 세션별 팝업 숨김 기능 추가
    • 디바이스별 앱 스토어 링크 정확도 향상 (iPhone, iPad, Mac 구분)
  • 개선 사항

    • 전사 디자인 시스템 토큰 적용으로 UI 일관성 강화
    • 관리자 페이지 헤더 렌더링 최적화
    • Chromatic 배포 프로세스 개선

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

suhyun113 and others added 30 commits January 5, 2026 01:21
- Swiper 클릭 이벤트 차단 옵션 비활성화
- 터치 관련 CSS 속성 추가 (touch-action, pointer-events)
- Popup 링크 이동 방식 window.open → location.href 변경
- 디버깅용 콘솔 로그 추가
- iPhone: https://apps.apple.com 링크 사용 (웹뷰에서 작동)
- iPad, iPod, Mac: itms-apps:// 딥링크 유지
- 관련 테스트 코드 업데이트
- 닫기/배경 클릭 시 sessionStorage에 저장
- 다시 보지 않기는 기존대로 localStorage 7일 유지
…psis-MOA-510

[feature] SNS 링크 표시 방식을 개선한다
…r-MOA-517

[feature] 관리자 로그인 페이지에서 공통 헤더를 노출한다
…ucture-MOA-482

[refactor] 전역 레이어링을 위한 Portal 모달 구조를 도입한다
…-MOA-519

[fix] 모바일 앱에서 앱스토어 배너/팝업 링크 이동 안되는 문제 수정
@seongwon030 seongwon030 requested a review from oesnuj January 15, 2026 03:56
@seongwon030 seongwon030 self-assigned this Jan 15, 2026
@vercel
Copy link

vercel bot commented Jan 15, 2026

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

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Jan 15, 2026 3:56am

@seongwon030 seongwon030 added 💻 FE Frontend 📈 release 릴리즈 배포 labels Jan 15, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 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

개요

기존 Modal 컴포넌트를 PortalModal과 ModalLayout 시스템으로 교체합니다. DOM에 modal-root을 추가하고, 스토리북 설정을 업데이트하며, 모달 관련 스타일을 테마 토큰으로 리팩토링합니다. 앱 스토어 링크 처리 및 팝업 세션 관리도 개선됩니다.

변경 사항

집단 / 파일 변경 요약
모달 시스템 교체
frontend/src/components/common/Modal/Modal.tsx, frontend/src/components/common/Modal/Modal.styles.ts, frontend/src/components/common/Modal/ModalLayout.tsx, frontend/src/components/common/Modal/PortalModal.tsx
기존 Modal 컴포넌트 제거. 새로운 PortalModal 컴포넌트 추가 (스크롤 잠금, 백드롭 클릭 처리, 포탈 렌더링). ModalLayout 컴포넌트 추가 (헤더, 설명, 본문 레이아웃). Modal.styles에서 Overlay와 ContentWrapper 프롭 제거, StandardLayout 추가 및 색상 토큰화.
모달 스토리북
frontend/src/components/common/Modal/ModalLayout.stories.tsx, frontend/src/components/common/PortalModal.stories.tsx
Modal에서 ModalLayout으로 마이그레이션. isOpen 제어 제거, width 프로퍼티 추가. 새로운 PortalModal 스토리 추가 (Default, NoBackdropClose).
스토리북 설정
frontend/.storybook/preview.ts
modal-root DOM 요소 자동 생성 데코레이터 추가 (없으면 생성).
HTML 및 DOM
frontend/index.html, frontend/src/styles/zIndex.ts
<div id="modal-root"></div> 추가. Z_INDEX 객체에서 modal 속성 제거.
모달 사용 업데이트
frontend/src/components/application/modals/ApplicationSelectModal.tsx, frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx, frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx, frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
ApplicationSelectModal: options → applicationOptions, onSelect → onOptionSelect로 프롭 이름 변경. PortalModal로 교체. PhotoModal: 수동 오버레이 마크업을 PortalModal로 교체, 스크롤 효과 제거. LoginTab: Header 컴포넌트 추가. ClubApplyButton: 프롭 이름 업데이트.
스타일 테마 토큰화
frontend/src/components/common/Modal/Modal.styles.ts, frontend/src/components/application/modals/ApplicationSelectModal.styles.ts, frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts, frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts
하드코딩된 HEX 색상값을 theme colors 객체(예: colors.gray[400], colors.base.white)로 교체. ModalOverlay 제거.
헤더 로직
frontend/src/components/common/Header/Header.tsx
/admin/login 경로 감지 추가. SearchBox는 관리자 페이지가 아닐 때만 렌더링. AdminProfile은 관리자 로그인 페이지가 아닐 때만 렌더링.
앱 스토어 링크
frontend/src/utils/appStoreLink.ts, frontend/src/utils/appStoreLink.test.ts
APP_STORE_LINKS: ios를 iphone과 apple로 분리. getAppStoreLink(): iPhone/iPad/Mac 감지 로직 분리 업데이트. 테스트 케이스 추가.
팝업 세션 관리
frontend/src/pages/MainPage/components/Popup/Popup.tsx
POPUP_SESSION_KEY 상수 추가. sessionStorage를 이용한 세션별 팝업 억제 기능 추가.
빌드 설정
frontend/package.json
chromatic 스크립트: dotenv로 환경변수 로드 방식으로 변경.
기타 스타일
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts
ClubDetailFooterContainer에서 z-index 선언 제거.

예상 코드 리뷰 난이도

🎯 3 (중간) | ⏱️ ~20분

관련 PR

제안 레이블

💻 FE, 📈 release, 🔨 Refactor, 🛠Fix

제안 검토자

  • lepitaaar
  • oesnuj
  • suhyun113
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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.
Title check ✅ Passed PR 제목은 '[release] FE v1.1.15'로, 이것은 프론트엔드 버전 릴리스임을 명확하게 나타냅니다. 변경사항 요약에서 모달 리팩토링, 버그 수정, UX 개선 등 다양한 작업이 포함되어 있으며, 제목은 이 모든 변경사항을 포괄하는 릴리스 버전으로 표현하고 있습니다. 제목은 간결하고 명확하며, 팀원이 히스토리를 훑어볼 때 주요 변경사항(릴리스)을 이해할 수 있습니다.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 [release] FE v1.1.15 [release] FE v1.1.15 팝업 및 앱스토어링크 관련 버그 해결 Jan 15, 2026
Copy link
Member

@oesnuj oesnuj 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 f535967 into main Jan 15, 2026
2 of 3 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: 4

🤖 Fix all issues with AI agents
In `@frontend/package.json`:
- Line 17: package.json의 "chromatic" 스크립트가 "dotenv chromatic"을 호출하지만
devDependencies에 dotenv-cli가 없어 실행 시 `dotenv: command not found` 오류가 발생합니다;
devDependencies에 "dotenv-cli"를 추가하고 package.json의 의존성 목록(devDependencies)에 적절한
버전을 명시한 후 npm install 또는 yarn install로 의존성을 설치하여 "chromatic" 스크립트가 정상 실행되도록
수정하세요.

In
`@frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts`:
- Around line 195-197: The `${media.tablet}` media-query block in
ClubProfileCard.styles.ts is missing its closing brace which breaks the
styled-component template; locate the `${media.tablet} { ...` block in
ClubProfileCard.styles.ts and add the corresponding `}` before the template
literal end so the media query is properly closed and the style compiles.
- Around line 160-163: The SocialText styled component sets display:
inline-block but uses the gap property which has no effect on inline-block;
update SocialText to use display: inline-flex (or display: flex) if you want to
keep gap, or remove the gap property and rely on margin spacing between child
elements if you must keep inline-block; locate the SocialText declaration and
either change its display to inline-flex or delete the gap: 4px line
accordingly.
🧹 Nitpick comments (19)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (2)

165-166: 하드코딩된 색상 값이 테마 토큰과 일치하지 않습니다.

이 PR의 목적이 하드코딩된 색상을 테마 토큰으로 교체하는 것인데, #009CF6는 새로운 하드코딩된 값입니다. 일관성을 위해 테마 토큰을 사용하는 것이 좋습니다.

♻️ 테마 토큰 사용 제안
 export const SocialUrl = styled.span`
-  color: `#009CF6`;
+  color: ${colors.accent[1][900]};

또는 해당 색상이 테마에 없다면 colors.ts에 새로운 토큰을 추가하는 것을 고려해 주세요.


75-75: CSS 변수와 테마 토큰의 혼용 방식을 검토해 주세요.

var(--Gray-400, ${colors.gray[400]}) 패턴은 CSS 변수가 정의되지 않은 경우 폴백으로만 작동합니다. 다른 스타일들은 테마 토큰을 직접 사용하고 있어 일관성이 떨어집니다.

♻️ 일관된 테마 토큰 사용 제안
-  border: 0.5px solid var(--Gray-400, ${colors.gray[400]});
+  border: 0.5px solid ${colors.gray[400]};
frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx (2)

12-12: Import 순서 개선 권장

Header import가 styled imports (LoginTab.styles) 뒤에 위치해 있습니다. 일반적인 컨벤션에 따라 컴포넌트 import를 styled imports 앞에 배치하는 것이 좋습니다.

💡 제안된 수정
 import useTrackPageView from '@/hooks/useTrackPageView';
+import Header from '@/components/common/Header/Header';
 import * as Styled from './LoginTab.styles';
-import Header from '@/components/common/Header/Header';

56-58: 들여쓰기 일관성 확인 필요

Fragment 내부의 LoginContainer와 그 자식 요소들의 들여쓰기가 Header와 일치하지 않습니다. 코드 가독성을 위해 일관된 들여쓰기를 적용하는 것이 좋습니다.

💡 제안된 수정
   return (
     <>
-    <Header />
-    <Styled.LoginContainer>
+      <Header />
+      <Styled.LoginContainer>
       ...
-    </Styled.LoginContainer>
+      </Styled.LoginContainer>
     </>
   );

Also applies to: 131-131

frontend/src/pages/MainPage/components/Popup/Popup.tsx (1)

97-106: handleDontShowAgain에서 sessionStorage 설정 누락 확인

handleClosesessionStorage를 설정하지만, handleDontShowAgainlocalStorage만 설정합니다. 현재 구현에서는 setIsOpen(false)가 직접 호출되어 즉시 닫히므로 동작에는 문제가 없습니다. 하지만 일관성을 위해 handleDontShowAgain에도 sessionStorage 설정을 추가하는 것을 고려해 볼 수 있습니다.

💡 제안된 수정 (선택사항)
   const handleDontShowAgain = () => {
     const abGroup = getABTestGroup();
     trackEvent(USER_EVENT.MAIN_POPUP_CLOSED, {
       popupType: 'app_download',
       action: 'dont_show_again',
       abTestGroup: abGroup,
     });
     localStorage.setItem(POPUP_STORAGE_KEY, Date.now().toString());
+    sessionStorage.setItem(POPUP_SESSION_KEY, 'true');
     setIsOpen(false);
   };
frontend/src/components/application/modals/ApplicationSelectModal.tsx (2)

35-35: 불필요한 중괄호 제거 권장

화살표 함수에서 단일 표현식 호출 시 중괄호가 불필요합니다.

💡 제안된 수정
-          onClick={() => {onOptionSelect(application)}}
+          onClick={() => onOptionSelect(application)}

56-60: JSX 포매팅 일관성 개선 권장

ApplicationOptions 컴포넌트의 props와 닫는 태그의 들여쓰기가 일관되지 않습니다.

💡 제안된 수정
       <ModalLayout title='지원서 선택' onClose={onClose} width="500px">
-        <ApplicationOptions applicationOptions={applicationOptions} 
-        onOptionSelect={onOptionSelect} 
-      />
+        <ApplicationOptions
+          applicationOptions={applicationOptions}
+          onOptionSelect={onOptionSelect}
+        />
       </ModalLayout>
frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx (1)

39-46: 불필요한 중복 체크 제거 고려

Line 39의 if (!isOpen) return null; 체크는 PortalModal 내부에서 이미 isOpenfalse일 때 null을 반환하므로 중복됩니다. 제거해도 동작에 영향이 없습니다.

♻️ 제안된 변경사항
-  if (!isOpen) return null;
-
-  return (
+  return (
     <PortalModal 
       isOpen={isOpen} 
       onClose={onClose}
       closeOnBackdrop={true}
-      >
+    >
frontend/.storybook/preview.ts (1)

5-17: modal-root 생성 타이밍 및 Story 렌더링 방식 확인 필요

두 가지 잠재적 이슈가 있습니다:

  1. 타이밍 이슈: useEffect는 첫 렌더 이후에 실행되므로, 모달 컴포넌트가 modal-root가 생성되기 전에 렌더링을 시도할 수 있습니다. 동기적으로 처리하는 것이 더 안전합니다.

  2. Story 렌더링: Storybook에서는 Story()보다 <Story />로 렌더링하는 것이 권장됩니다.

♻️ 제안된 변경사항
 decorators: [
   (Story) => {
-    useEffect(() => {
-      if (!document.getElementById('modal-root')) {
-        const modalRoot = document.createElement('div');
-        modalRoot.id = 'modal-root';
-        document.body.appendChild(modalRoot);
-      }
-    }, []);
+    if (!document.getElementById('modal-root')) {
+      const modalRoot = document.createElement('div');
+      modalRoot.id = 'modal-root';
+      document.body.appendChild(modalRoot);
+    }
 
-    return Story();
+    return <Story />;
   },
 ],
frontend/src/components/common/Modal/PortalModal.tsx (1)

18-21: 스크롤 잠금 로직 검토

스크롤 잠금 로직이 올바르게 동작합니다. 다만 overflow를 빈 문자열로 리셋하면 기존에 body에 설정된 커스텀 스크롤 스타일이 있을 경우 손실될 수 있습니다. 현재 프로젝트에서 문제가 없다면 괜찮지만, 더 안전한 방법을 원하시면 이전 값을 저장 후 복원하는 방식을 고려해 볼 수 있습니다.

♻️ 선택적 개선안
 useEffect(() => {
+  const originalOverflow = document.body.style.overflow;
   if (isOpen) document.body.style.overflow = 'hidden';
-  return () => { document.body.style.overflow = ''; };
+  return () => { document.body.style.overflow = originalOverflow; };
 }, [isOpen]);
frontend/src/components/common/Modal/ModalLayout.tsx (1)

19-24: 접근성 개선: aria-labelledbyaria-describedby 추가 권장

role="dialog"aria-modal="true"는 잘 적용되어 있습니다. 스크린 리더 사용자 경험을 더 향상시키기 위해 aria-labelledbyaria-describedby 속성을 추가하면 좋겠습니다.

♿ 접근성 개선을 위한 제안
+import { useId, ReactNode } from 'react';
-import { ReactNode } from 'react';
 const ModalLayout = ({
   onClose,
   title,
   description,
   children,
   width,
 }: ModalLayoutProps) => {
+  const titleId = useId();
+  const descriptionId = useId();
+
   return (
     <Styled.StandardLayout 
       $width={width} 
       role="dialog" 
       aria-modal="true"
+      aria-labelledby={title ? titleId : undefined}
+      aria-describedby={description ? descriptionId : undefined}
     >
       {(title || onClose) && (
         <Styled.Header>
-          {title && <Styled.Title>{title}</Styled.Title>}
+          {title && <Styled.Title id={titleId}>{title}</Styled.Title>}
-      {description && <Styled.Description>{description}</Styled.Description>}
+      {description && <Styled.Description id={descriptionId}>{description}</Styled.Description>}
frontend/src/components/common/Modal/PortalModal.stories.tsx (2)

36-49: 인라인 스타일 대신 styled-components 사용 고려

ModalContent가 인라인 스타일로 정의되어 있습니다. Storybook 데모 목적으로는 괜찮지만, 프로젝트 전체에서 styled-components를 사용하고 있으므로 일관성을 위해 styled-components를 사용하거나 기존 ModalLayout 컴포넌트를 재사용하는 것을 고려해 볼 수 있습니다.


86-118: 중복 코드 리팩토링 가능

DefaultNoBackdropClose 스토리의 render 함수가 거의 동일한 구조를 가지고 있습니다. 스토리 파일에서는 명시적인 코드가 선호되기도 하지만, 필요시 공통 render 함수를 추출하여 재사용할 수 있습니다.

frontend/src/components/common/Modal/ModalLayout.stories.tsx (1)

66-99: 스토리 간 일관성 확인 필요

LongContent 스토리에서만 onClose 핸들러가 정의되어 있습니다. 닫기 버튼을 보여주기 위한 의도적인 선택이라면 괜찮지만, 다른 스토리에서도 닫기 버튼을 테스트하려면 동일하게 추가하는 것이 좋습니다.

frontend/src/components/common/Modal/Modal.styles.ts (1)

5-14: 불필요한 transition 속성 확인

background가 이제 고정값(rgba(0, 0, 0, 0.6))이므로, transition: background-color 0.2s ease가 더 이상 적용되지 않을 수 있습니다. PortalModal에서 mount/unmount로 제어한다면 이 transition은 제거해도 됩니다. 또는 opacity 기반 fade 효과를 원한다면 별도 구현이 필요합니다.

frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts (2)

202-216: 하드코딩된 색상을 테마 색상으로 변경 권장

Line 215에서 hover 상태의 비활성 테두리 색상으로 '#ddd'가 하드코딩되어 있습니다. 다른 부분에서는 테마 색상을 사용하고 있으므로, 일관성을 위해 colors.gray[400] (값: #DCDCDC)을 사용하는 것을 권장합니다.

♻️ 테마 색상 사용 제안
   &:hover {
-    border-color: ${({ isActive }) => (isActive ? colors.primary[900] : '#ddd')};
+    border-color: ${({ isActive }) => (isActive ? colors.primary[900] : colors.gray[400])};
   }

111-152: NavButton의 배경색 테마 적용 고려 (선택사항)

rgba(255, 255, 255, 0.9) 등의 하드코딩된 값이 사용되고 있습니다. 이 파일의 다른 부분에서 테마 색상을 사용하고 있으므로, 필요시 테마 색상과 opacity를 조합하여 사용할 수 있습니다. 다만 현재 구현도 정상적으로 동작합니다.

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

29-38: detectPlatformgetAppStoreLink 간 일관성 확인

detectPlatformmacintoshiOS로 분류하는데, 이는 기술적으로 정확하지 않습니다 (macOS는 iOS가 아님). 현재 코드에서 detectPlatform의 반환값이 어디서 사용되는지에 따라 이 분류가 문제가 될 수 있습니다.

만약 detectPlatform이 "Apple 기기"를 의미하는 용도로 사용된다면, 타입과 함수명을 'Apple' | 'Android' | 'Other'로 변경하는 것이 더 명확할 수 있습니다.

frontend/src/utils/appStoreLink.test.ts (1)

97-135: iPod 테스트 케이스 누락

getAppStoreLink 함수에서 /ipad|ipod|macintosh/ 정규식으로 iPod을 처리하고 있지만, 해당 분기에 대한 테스트가 없습니다. 테스트 커버리지 완성도를 위해 iPod 테스트 케이스를 추가하는 것을 권장합니다.

🧪 iPod 테스트 케이스 추가 제안
    it('Mac에서 itms-apps 딥링크를 반환한다', () => {
      Object.defineProperty(global, 'navigator', {
        value: {
          userAgent:
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
        },
        writable: true,
        configurable: true,
      });

      expect(getAppStoreLink()).toBe(APP_STORE_LINKS.apple);
    });

+    it('iPod에서 itms-apps 딥링크를 반환한다', () => {
+      Object.defineProperty(global, 'navigator', {
+        value: {
+          userAgent:
+            'Mozilla/5.0 (iPod touch; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
+        },
+        writable: true,
+        configurable: true,
+      });
+
+      expect(getAppStoreLink()).toBe(APP_STORE_LINKS.apple);
+    });
📜 Review details

Configuration used: Organization 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 1fcfb07 and b8e8681.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • frontend/.storybook/preview.ts
  • frontend/index.html
  • frontend/package.json
  • frontend/src/components/application/modals/ApplicationSelectModal.styles.ts
  • frontend/src/components/application/modals/ApplicationSelectModal.tsx
  • frontend/src/components/common/Header/Header.tsx
  • frontend/src/components/common/Modal/Modal.styles.ts
  • frontend/src/components/common/Modal/Modal.tsx
  • frontend/src/components/common/Modal/ModalLayout.stories.tsx
  • frontend/src/components/common/Modal/ModalLayout.tsx
  • frontend/src/components/common/Modal/PortalModal.stories.tsx
  • frontend/src/components/common/Modal/PortalModal.tsx
  • frontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts
  • frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts
  • frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts
  • frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsx
  • frontend/src/pages/MainPage/components/Popup/Popup.tsx
  • frontend/src/styles/zIndex.ts
  • frontend/src/utils/appStoreLink.test.ts
  • frontend/src/utils/appStoreLink.ts
💤 Files with no reviewable changes (3)
  • frontend/src/styles/zIndex.ts
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.ts
  • frontend/src/components/common/Modal/Modal.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 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/components/ClubProfileCard/ClubProfileCard.styles.ts
🧬 Code graph analysis (6)
frontend/src/utils/appStoreLink.test.ts (1)
frontend/src/utils/appStoreLink.ts (2)
  • getAppStoreLink (12-27)
  • APP_STORE_LINKS (1-8)
frontend/src/components/application/modals/ApplicationSelectModal.tsx (1)
frontend/src/types/application.ts (1)
  • ApplicationForm (65-68)
frontend/src/components/common/Modal/PortalModal.stories.tsx (2)
frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts (1)
  • ModalContent (5-22)
frontend/src/components/common/Modal/ModalLayout.stories.tsx (1)
  • Default (38-45)
frontend/src/components/common/Modal/Modal.styles.ts (2)
frontend/src/styles/zIndex.ts (1)
  • Z_INDEX (1-5)
frontend/src/styles/theme/colors.ts (1)
  • colors (1-81)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (2)
frontend/src/styles/theme/colors.ts (1)
  • colors (1-81)
frontend/src/styles/mediaQuery.ts (1)
  • media (8-14)
frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts (2)
frontend/src/styles/theme/colors.ts (1)
  • colors (1-81)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (1)
  • ClubName (102-113)
🔇 Additional comments (20)
frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts (1)

9-9: 테마 토큰으로의 마이그레이션이 적절합니다.

하드코딩된 색상을 colors.base.white, colors.gray[100] 등의 테마 토큰으로 교체한 것은 디자인 시스템 일관성 측면에서 좋은 변경입니다.

Also applies to: 65-65, 83-83, 87-87, 193-193

frontend/src/components/common/Header/Header.tsx (1)

17-17: LGTM! 관리자 로그인 페이지 감지 로직이 적절합니다.

isAdminLoginPage 변수를 통해 로그인 페이지에서는 AdminProfile을 숨기고, 일반 관리자 페이지에서만 표시하는 로직이 명확합니다. 기존 isAdminPage 패턴과 일관성 있게 구현되었습니다.

Also applies to: 89-90

frontend/src/pages/MainPage/components/Popup/Popup.tsx (1)

31-40: LGTM! 세션 기반 팝업 숨김 로직이 적절하게 구현되었습니다.

sessionStorage 체크를 먼저 수행하여 빠르게 short-circuit하고, 그 후 localStorage의 7일 숨김 로직을 확인하는 순서가 효율적입니다. 세션 내 팝업 재표시 방지와 "다시 보지 않기" 기능이 잘 분리되어 있습니다.

frontend/src/components/application/modals/ApplicationSelectModal.tsx (1)

44-62: LGTM! PortalModal/ModalLayout 패턴으로의 리팩터링이 잘 되었습니다.

기존 Modal 컴포넌트에서 PortalModal과 ModalLayout으로의 전환이 적절하게 이루어졌습니다. closeOnBackdrop prop을 통한 배경 클릭 처리와 props 이름 변경(applicationOptions, onOptionSelect)이 명확합니다.

frontend/src/components/application/modals/ApplicationSelectModal.styles.ts (1)

1-35: LGTM! 테마 토큰 적용이 적절합니다.

하드코딩된 색상 값을 테마 토큰(colors.gray, colors.primary, colors.base)으로 교체하여 일관된 디자인 시스템을 따르고 있습니다. hover 상태의 색상 전환도 자연스럽게 구현되었습니다.

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

113-118: LGTM!

ApplicationSelectModal의 새로운 prop 인터페이스(applicationOptions, onOptionSelect)에 맞게 올바르게 업데이트되었습니다. 변경사항이 일관성 있게 적용되었습니다.

frontend/index.html (1)

65-65: LGTM!

React 포털 기반 모달을 위한 modal-root 요소가 올바르게 추가되었습니다. #root 다음에 배치하여 모달이 메인 앱 DOM 트리 외부에서 렌더링되도록 하는 표준 패턴입니다.

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

47-136: LGTM!

PortalModal로의 마이그레이션이 잘 되었습니다. e.stopPropagation()으로 모달 콘텐츠 클릭 시 배경 클릭 이벤트 전파를 올바르게 방지하고 있습니다.

frontend/src/components/common/Modal/PortalModal.tsx (1)

28-39: LGTM!

React 포털을 사용한 모달 구현이 잘 되어 있습니다. ContentWrapper에서 stopPropagation으로 콘텐츠 클릭 시 배경 클릭 핸들러 호출을 방지하는 패턴이 적절합니다.

frontend/src/components/common/Modal/ModalLayout.tsx (1)

25-41: LGTM!

조건부 렌더링 로직이 잘 구현되어 있습니다. title이나 onClose가 있을 때만 헤더를 렌더링하고, 닫기 버튼에 aria-labeltype="button"이 올바르게 적용되어 있습니다.

frontend/src/components/common/Modal/PortalModal.stories.tsx (1)

52-83: LGTM!

상태 관리를 위한 useState 훅과 handleClose 함수를 사용한 스토리 구현이 적절합니다. args.onClose()를 호출하여 Storybook의 action 로깅과 연동되도록 잘 처리되어 있습니다.

frontend/src/components/common/Modal/ModalLayout.stories.tsx (2)

37-45: LGTM!

기본 스토리 설정이 잘 구성되어 있습니다. title, description, children, width 속성을 명확하게 보여주고 있습니다.


47-64: LGTM!

WideModalNoDescription 스토리가 컴포넌트의 다양한 사용 사례를 잘 보여주고 있습니다.

frontend/src/components/common/Modal/Modal.styles.ts (2)

25-35: 모바일 화면에서 margin 확인 필요

margin: 24px가 설정되어 있어, 매우 작은 화면에서 모달 콘텐츠 영역이 줄어들 수 있습니다. max-width: 100%와 함께 max-width: calc(100% - 48px) 또는 미디어 쿼리를 사용하여 모바일에서 margin을 조정하는 것을 고려해 보세요.


37-66: LGTM!

테마 기반 색상으로 잘 마이그레이션되었습니다. colors.gray[400], colors.gray[600]을 사용하여 디자인 시스템과 일관성을 유지하고 있습니다.

frontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.ts (2)

3-9: LGTM!

테마 색상(colors.base.white)을 사용하여 일관성 있게 스타일링되었습니다.


41-69: LGTM!

헤더와 텍스트 요소들이 테마 색상을 잘 사용하고 있습니다. colors.base.white, colors.base.black, colors.gray[700]으로 일관성 있게 적용되었습니다.

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

1-8: 상수 구조 변경 승인

iosiphoneapple로 분리하여 디바이스별 링크 형식을 명확하게 구분한 점이 좋습니다. iPhone 웹뷰에서 HTTPS 링크가 필요한 경우를 적절히 처리했습니다.

frontend/src/utils/appStoreLink.test.ts (2)

164-173: 상수 검증 테스트 승인

APP_STORE_LINKS 상수에 대한 검증 테스트가 잘 구성되어 있습니다. 앱 ID(6755062085)가 iPhone과 Apple 링크 모두에 포함되어 있는지 확인하고, URL 스킴이 올바른지 검증하는 방식이 좋습니다.


7-16: 테스트 설정 및 정리 로직 승인

afterEach에서 navigator를 원래 값으로 복원하는 방식이 적절합니다. 테스트 간 격리가 잘 되어 있습니다.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"chromatic": "chromatic --project-token=$CHROMATIC_PROJECT_TOKEN",
"chromatic": "dotenv chromatic",
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

🧩 Analysis chain

🏁 Script executed:

# Read the frontend/package.json file to verify the claims
cat -n frontend/package.json

Repository: Moadong/moadong

Length of output: 4592


🏁 Script executed:

# Search for dotenv-cli in the entire codebase
rg "dotenv-cli" --type json

Repository: Moadong/moadong

Length of output: 368


🏁 Script executed:

# Check for any alternative dotenv packages or references
rg "dotenv" frontend/package.json -A 2 -B 2

Repository: Moadong/moadong

Length of output: 426


🏁 Script executed:

# Check what package in package-lock.json depends on dotenv-cli
cat frontend/package-lock.json | grep -B 10 '"dotenv-cli"' | head -30

Repository: Moadong/moadong

Length of output: 510


🏁 Script executed:

# Check if chromatic package brings dotenv-cli as dependency
python3 << 'EOF'
import json
import re

with open('frontend/package-lock.json', 'r') as f:
    lock_data = json.load(f)

# Find dotenv-cli entry and check who requires it
packages = lock_data.get('packages', {})
for pkg_name, pkg_info in packages.items():
    if 'dotenv-cli' in pkg_name or pkg_name == 'node_modules/dotenv-cli':
        print(f"Found: {pkg_name}")
        print(f"Version: {pkg_info.get('version')}")
        print(f"Direct entry: {pkg_name == 'node_modules/dotenv-cli'}")
        print()

# Check chromatic's dependencies
for pkg_name, pkg_info in packages.items():
    if 'chromatic' in pkg_name and 'node_modules' in pkg_name:
        deps = pkg_info.get('dependencies', {})
        if deps:
            print(f"{pkg_name} dependencies: {list(deps.keys())[:10]}")
EOF

Repository: Moadong/moadong

Length of output: 145


🏁 Script executed:

# Check the exact section where dotenv-cli should appear in devDependencies
sed -n '47,103p' frontend/package.json | grep -i "dotenv"

Repository: Moadong/moadong

Length of output: 41


🏁 Script executed:

# Search for all dotenv references in package.json only
grep "dotenv" frontend/package.json

Repository: Moadong/moadong

Length of output: 127


🏁 Script executed:

# Check if dotenv-cli might be a transitive dependency by examining package-lock.json structure more carefully
python3 << 'EOF'
import json

with open('frontend/package-lock.json', 'r') as f:
    lock_data = json.load(f)

packages = lock_data.get('packages', {})

# Find all entries mentioning dotenv-cli
dotenv_cli_entries = {k: v for k, v in packages.items() if 'dotenv-cli' in k}
print("dotenv-cli entries in package-lock.json:")
for entry_name, entry_data in dotenv_cli_entries.items():
    print(f"\nEntry: {entry_name}")
    print(f"Version: {entry_data.get('version')}")
    
# Check if chromatic depends on dotenv-cli
chromatic_entries = {k: v for k, v in packages.items() if 'chromatic' in k and 'node_modules' in k}
for entry_name, entry_data in chromatic_entries.items():
    if 'dependencies' in entry_data:
        print(f"\n{entry_name} dependencies:")
        if 'dotenv-cli' in entry_data['dependencies']:
            print(f"  ✓ Has dotenv-cli: {entry_data['dependencies']['dotenv-cli']}")
        else:
            print(f"  ✗ No dotenv-cli dependency")
EOF

Repository: Moadong/moadong

Length of output: 403


🏁 Script executed:

# Check if there's an actual node_modules/dotenv-cli directory
ls -la frontend/node_modules/dotenv-cli 2>&1 | head -20

Repository: Moadong/moadong

Length of output: 138


🏁 Script executed:

# Check the root-level requirements in package-lock.json
python3 << 'EOF'
import json

with open('frontend/package-lock.json', 'r') as f:
    lock_data = json.load(f)

# Check the root package requirements
root_package = lock_data.get('packages', {}).get('', {})
dev_deps = root_package.get('devDependencies', {})

if 'dotenv-cli' in dev_deps:
    print(f"✓ dotenv-cli IS listed in package-lock.json root devDependencies: {dev_deps['dotenv-cli']}")
else:
    print("✗ dotenv-cli is NOT listed in package-lock.json root devDependencies")

# Show all packages that require dotenv-cli
print("\nPackages with dotenv-cli as dependency:")
for pkg_name, pkg_info in lock_data.get('packages', {}).items():
    if 'dependencies' in pkg_info and 'dotenv-cli' in pkg_info['dependencies']:
        print(f"  - {pkg_name}: {pkg_info['dependencies']['dotenv-cli']}")
EOF

Repository: Moadong/moadong

Length of output: 173


dotenv-cli 패키지 의존성 누락

dotenv chromatic 명령어를 실행하려면 dotenv-cli 패키지가 필요합니다. 현재 devDependencies에 dotenv-cli가 없어서 스크립트 실행 시 dotenv: command not found 에러가 발생합니다.

dotenv-cli를 devDependencies에 추가하세요.

의존성 추가 제안
    "chromatic": "^11.25.0",
+   "dotenv-cli": "^11.0.0",
    "copy-webpack-plugin": "^13.0.0",
🤖 Prompt for AI Agents
In `@frontend/package.json` at line 17, package.json의 "chromatic" 스크립트가 "dotenv
chromatic"을 호출하지만 devDependencies에 dotenv-cli가 없어 실행 시 `dotenv: command not
found` 오류가 발생합니다; devDependencies에 "dotenv-cli"를 추가하고 package.json의 의존성
목록(devDependencies)에 적절한 버전을 명시한 후 npm install 또는 yarn install로 의존성을 설치하여
"chromatic" 스크립트가 정상 실행되도록 수정하세요.

Comment on lines 160 to 163
export const SocialText = styled.span`
word-break: break-all;
display: inline-block;
gap: 4px;
`;
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 | 🔴 Critical

gap 속성은 inline-block 요소에서 작동하지 않습니다.

gap 속성은 display: flex 또는 display: grid 컨테이너에서만 유효합니다. inline-block으로 설정된 요소에는 효과가 없습니다.

🐛 수정 제안

gap을 사용하려면 display: inline-flex로 변경하거나, inline-block을 유지하려면 gap 속성을 제거하세요:

 export const SocialText = styled.span`
-  display: inline-block;
-  gap: 4px;
+  display: inline-flex;
+  gap: 4px;
 `;

또는 inline-block을 유지하고 gap을 제거:

 export const SocialText = styled.span`
   display: inline-block;
-  gap: 4px;
 `;
📝 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
export const SocialText = styled.span`
word-break: break-all;
display: inline-block;
gap: 4px;
`;
export const SocialText = styled.span`
display: inline-flex;
gap: 4px;
`;
🤖 Prompt for AI Agents
In
`@frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts`
around lines 160 - 163, The SocialText styled component sets display:
inline-block but uses the gap property which has no effect on inline-block;
update SocialText to use display: inline-flex (or display: flex) if you want to
keep gap, or remove the gap property and rely on margin spacing between child
elements if you must keep inline-block; locate the SocialText declaration and
either change its display to inline-flex or delete the gap: 4px line
accordingly.

Comment on lines 195 to 197
${media.tablet} {
background-color: #f5f5f5;
}
background-color: ${colors.gray[100]};
`;
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 | 🔴 Critical

구문 오류: tablet 미디어 쿼리 블록의 닫는 중괄호가 누락되었습니다.

${media.tablet} 블록에 닫는 중괄호 }가 없어서 스타일이 올바르게 적용되지 않거나 빌드 오류가 발생할 수 있습니다.

🐛 수정 제안
   ${media.tablet} {
     background-color: ${colors.gray[100]};
+  }
 `;
📝 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
${media.tablet} {
background-color: #f5f5f5;
}
background-color: ${colors.gray[100]};
`;
${media.tablet} {
background-color: ${colors.gray[100]};
}
`;
🤖 Prompt for AI Agents
In
`@frontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.ts`
around lines 195 - 197, The `${media.tablet}` media-query block in
ClubProfileCard.styles.ts is missing its closing brace which breaks the
styled-component template; locate the `${media.tablet} { ...` block in
ClubProfileCard.styles.ts and add the corresponding `}` before the template
literal end so the media query is properly closed and the style compiles.

Comment on lines +15 to 22
// iPhone은 웹뷰 대응을 위해 HTTPS 링크 사용
if (/iphone/.test(userAgent)) {
return APP_STORE_LINKS.iphone;
}
// iPad, iPod, Mac은 itms-apps 딥링크 사용
if (/ipad|ipod|macintosh/.test(userAgent)) {
return APP_STORE_LINKS.apple;
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n frontend/src/utils/appStoreLink.ts

Repository: Moadong/moadong

Length of output: 1461


🌐 Web query:

itms-apps URL scheme macOS Safari Mac App Store

💡 Result:

Short answer

  • itms-apps:// is Apple’s App Store URL scheme used on iOS to open the App Store app directly; on macOS the equivalent scheme is macappstore: (and apps.apple.com web links are handled differently). [1][2][3]

Details and practical notes

  • iOS: use itms-apps://... (or the newer apps.apple.com web URLs which may redirect to an itms-apps/itms-appss handler) to launch the App Store from apps or links. [1][4]
  • macOS: the App Store app is the default handler for macappstore: URLs (macappstore://...), and Safari often treats apps.apple.com links as “universal”/special links that open the App Store app instead of staying in the browser. That means clicking an apps.apple.com link in Safari on macOS may launch App Store rather than navigating to the web preview. [2][3][6]
  • Workarounds: to view the web preview in Safari you can paste the apps.apple.com URL into the address bar (or use Request Desktop Website / open via contextual menu) so Safari loads the web page instead of invoking the App Store app. Extensions like “Stop The Mac App Store” demonstrate and document this behavior and workarounds. [2][3]

Examples

  • iOS-style: itms-apps://itunes.apple.com/us/app/id. [4]
  • macOS-style: macappstore://itunes.apple.com/us/app/id (App Store is the registered handler). [2][7]

Sources

  • Apple documentation on URL schemes. [1]
  • Jeff Johnson / Lapcat: how Safari/macOS handle App Store links and macappstore: behavior. [2][3]
  • Practical examples and usage notes (how to form itms-apps links). [4]

If you want, I can give exact example URLs for a specific app ID and show how Safari behaves step-by-step on macOS.


macOS와 iPad 데스크톱 모드에서 itms-apps:// 스킴 미동작 - 수정 필수

itms-apps:// URL 스킴은 iOS 전용이며 macOS에서는 작동하지 않습니다. macOS의 경우 올바른 scheme은 macappstore:// 이거나 apps.apple.com HTTPS 링크를 사용해야 합니다.

현재 코드의 문제점:

  • Mac 사용자(userAgent에 "macintosh" 포함)가 itms-apps:// 링크를 받으면 작동하지 않음
  • iPad 사용자가 "요청한 데스크톱 사이트" 옵션을 활성화하면 "Macintosh" user agent를 전송하여 같은 문제 발생
  • macintosh 감지 시 APP_STORE_LINKS.iphone(HTTPS 링크)을 사용하거나, 별도의 macOS 전용 링크(macappstore:// 또는 HTTPS)를 제공해야 함

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