[release] FE v1.1.15 팝업 및 앱스토어링크 관련 버그 해결#1047
Conversation
- Swiper 클릭 이벤트 차단 옵션 비활성화 - 터치 관련 CSS 속성 추가 (touch-action, pointer-events) - Popup 링크 이동 방식 window.open → location.href 변경 - 디버깅용 콘솔 로그 추가
This reverts commit bb4cfef.
- 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] 모바일 앱에서 앱스토어 배너/팝업 링크 이동 안되는 문제 수정
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| 집단 / 파일 | 변경 요약 |
|---|---|
모달 시스템 교체 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
- [refactor] 전역 레이어링을 위한 Portal 모달 구조를 도입한다 #1018 — 동일한 Modal/PortalModal/ModalLayout 리팩토링과 Storybook 데코레이터 추가를 포함하는 직접 연관 PR
- [fix] 모바일 앱에서 앱스토어 배너/팝업 링크 이동 안되는 문제 수정 #1041 — 동일한 appStoreLink.ts 수정 (ios를 iphone과 apple로 분리) 및 Popup.tsx의 POPUP_SESSION_KEY 변경 포함
- [refactor] z-index 기준으로 리팩토링한다 #908 — z-index 중앙화 및 Modal.styles Z_INDEX 사용 업데이트로 모달 스택 처리 관련
제안 레이블
💻 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
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 순서 개선 권장
Headerimport가 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 설정 누락 확인
handleClose는sessionStorage를 설정하지만,handleDontShowAgain은localStorage만 설정합니다. 현재 구현에서는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내부에서 이미isOpen이false일 때null을 반환하므로 중복됩니다. 제거해도 동작에 영향이 없습니다.♻️ 제안된 변경사항
- if (!isOpen) return null; - - return ( + return ( <PortalModal isOpen={isOpen} onClose={onClose} closeOnBackdrop={true} - > + >frontend/.storybook/preview.ts (1)
5-17:modal-root생성 타이밍 및 Story 렌더링 방식 확인 필요두 가지 잠재적 이슈가 있습니다:
타이밍 이슈:
useEffect는 첫 렌더 이후에 실행되므로, 모달 컴포넌트가modal-root가 생성되기 전에 렌더링을 시도할 수 있습니다. 동기적으로 처리하는 것이 더 안전합니다.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-labelledby와aria-describedby추가 권장
role="dialog"와aria-modal="true"는 잘 적용되어 있습니다. 스크린 리더 사용자 경험을 더 향상시키기 위해aria-labelledby와aria-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: 중복 코드 리팩토링 가능
Default와NoBackdropClose스토리의 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:detectPlatform과getAppStoreLink간 일관성 확인
detectPlatform은macintosh를iOS로 분류하는데, 이는 기술적으로 정확하지 않습니다 (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.
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (22)
frontend/.storybook/preview.tsfrontend/index.htmlfrontend/package.jsonfrontend/src/components/application/modals/ApplicationSelectModal.styles.tsfrontend/src/components/application/modals/ApplicationSelectModal.tsxfrontend/src/components/common/Header/Header.tsxfrontend/src/components/common/Modal/Modal.styles.tsfrontend/src/components/common/Modal/Modal.tsxfrontend/src/components/common/Modal/ModalLayout.stories.tsxfrontend/src/components/common/Modal/ModalLayout.tsxfrontend/src/components/common/Modal/PortalModal.stories.tsxfrontend/src/components/common/Modal/PortalModal.tsxfrontend/src/pages/AdminPage/auth/LoginTab/LoginTab.tsxfrontend/src/pages/ClubDetailPage/components/ClubApplyButton/ClubApplyButton.tsxfrontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.styles.tsfrontend/src/pages/ClubDetailPage/components/ClubProfileCard/ClubProfileCard.styles.tsfrontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.styles.tsfrontend/src/pages/ClubDetailPage/components/PhotoModal/PhotoModal.tsxfrontend/src/pages/MainPage/components/Popup/Popup.tsxfrontend/src/styles/zIndex.tsfrontend/src/utils/appStoreLink.test.tsfrontend/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으로의 전환이 적절하게 이루어졌습니다.
closeOnBackdropprop을 통한 배경 클릭 처리와 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-label과type="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!
WideModal과NoDescription스토리가 컴포넌트의 다양한 사용 사례를 잘 보여주고 있습니다.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: 상수 구조 변경 승인
ios를iphone과apple로 분리하여 디바이스별 링크 형식을 명확하게 구분한 점이 좋습니다. 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", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the frontend/package.json file to verify the claims
cat -n frontend/package.jsonRepository: Moadong/moadong
Length of output: 4592
🏁 Script executed:
# Search for dotenv-cli in the entire codebase
rg "dotenv-cli" --type jsonRepository: Moadong/moadong
Length of output: 368
🏁 Script executed:
# Check for any alternative dotenv packages or references
rg "dotenv" frontend/package.json -A 2 -B 2Repository: 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 -30Repository: 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]}")
EOFRepository: 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.jsonRepository: 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")
EOFRepository: 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 -20Repository: 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']}")
EOFRepository: 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" 스크립트가 정상 실행되도록 수정하세요.
| export const SocialText = styled.span` | ||
| word-break: break-all; | ||
| display: inline-block; | ||
| gap: 4px; | ||
| `; |
There was a problem hiding this comment.
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.
| 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.
| ${media.tablet} { | ||
| background-color: #f5f5f5; | ||
| } | ||
| background-color: ${colors.gray[100]}; | ||
| `; |
There was a problem hiding this comment.
구문 오류: 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.
| ${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.
| // 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; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n frontend/src/utils/appStoreLink.tsRepository: 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)를 제공해야 함
#️⃣연관된 이슈
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.