feat: PWA 관련 UX 작업#361
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @gajaeup's task in 2m 45s —— View job 증분 리뷰 완료이전 지적사항 해결 여부해결됨 ✅
미해결 ❌
새로 발견된 이슈없음 종합PWA 설치 기능 구현 자체는 잘 작성되었습니다:
다만 이전 리뷰의 Header/index.tsx import 순서 이슈가 아직 해결되지 않았습니다. 내부 모듈( |
| import { useState } from 'react'; | ||
|
|
||
| import { Button } from '@/components/ui/Button'; | ||
| import { usePWAInstall } from '@/hooks/usePWAInstall'; |
There was a problem hiding this comment.
[MEDIUM] Import 그룹 사이 빈 줄 누락
코드 스타일 규칙에 따르면 import 그룹 사이에 빈 줄이 필요합니다:
- React / Next.js
- (빈 줄)
- 외부 라이브러리
- (빈 줄)
- 내부 모듈
- (빈 줄)
- 상대 경로
| import { useState } from 'react'; | |
| import { Button } from '@/components/ui/Button'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { useState } from 'react'; | |
| import { Button } from '@/components/ui/Button'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; |
|
고생하셨어요! 버튼 배치나 디자인은 디자이너님과도 함께 논의해보는 것이 좋을 것 같아요 |
|
|
||
| return ( | ||
| <> | ||
| <Button className='text-body-02-m text-gray-700' onClick={handleClick} {...buttonProps}> |
There was a problem hiding this comment.
[MEDIUM] className 우선순위 이슈
현재 {...buttonProps} spread가 인라인 className 뒤에 위치하여, buttonProps에 className이 포함되면 인라인 className이 무시됩니다.
해결 방법:
cn()으로 병합하거나- spread를 먼저 배치하고 인라인 className을 뒤에 위치
| <Button className='text-body-02-m text-gray-700' onClick={handleClick} {...buttonProps}> | |
| <Button onClick={handleClick} {...buttonProps} className='text-body-02-m text-gray-700'> | |
| 앱 설치 | |
| </Button> |
또는 더 안전하게 병합:
<Button onClick={handleClick} {...buttonProps} className={cn('text-body-02-m text-gray-700', buttonProps.className)}>
증분 리뷰 완료이전 지적사항 해결 여부✅ Import 그룹 사이 빈 줄 누락 (PWAInstallButton/index.tsx) - 해결됨 새로 발견된 이슈없음 종합PWA 설치 기능 구현 자체는 잘 작성되었습니다:
다만 이전 리뷰의 className 우선순위 이슈가 아직 해결되지 않았습니다. 현재 |
|
|
||
| return ( | ||
| <> | ||
| <Button onClick={handleClick} {...buttonProps} className='text-body-02-m text-gray-700'> |
There was a problem hiding this comment.
[MEDIUM] className 우선순위 이슈 (재지적)
현재 {...buttonProps}가 onClick 뒤에 오고 className이 가장 마지막에 와서, buttonProps.className이 무시됩니다.
해결 방법:
- spread를 먼저 배치하고 나머지를 뒤에 (권장):
| <Button onClick={handleClick} {...buttonProps} className='text-body-02-m text-gray-700'> | |
| <Button {...buttonProps} onClick={handleClick} className='text-body-02-m text-gray-700'> | |
| 앱 설치 | |
| </Button> |
- 또는
cn()으로 안전하게 병합:
<Button onClick={handleClick} {...buttonProps} className={cn('text-body-02-m text-gray-700', buttonProps.className)}>|
|
||
| return ( | ||
| <> | ||
| <Button onClick={handleClick} {...buttonProps} className='text-body-02-m text-gray-700'> |
There was a problem hiding this comment.
[MEDIUM] className 우선순위 이슈 (미해결)
현재 {...buttonProps}가 인라인 className 앞에 위치하여, 소비자가 buttonProps로 전달한 className이 무시됩니다.
JSX에서는 뒤에 오는 prop이 앞의 prop을 덮어쓰므로, 현재 코드는 항상 'text-body-02-m text-gray-700'만 적용됩니다.
권장 해결 방법:
cn()으로 안전하게 병합 (가장 안전):
| <Button onClick={handleClick} {...buttonProps} className='text-body-02-m text-gray-700'> | |
| <Button onClick={handleClick} {...buttonProps} className={cn('text-body-02-m text-gray-700', buttonProps.className)}> | |
| 앱 설치 | |
| </Button> |
- 또는 소비자 className이 항상 우선하도록:
<Button {...buttonProps} onClick={handleClick} className='text-body-02-m text-gray-700'>방법 1을 권장합니다 (기본 스타일 + 소비자 스타일 병합).
| import { useState, useSyncExternalStore } from 'react'; | ||
| import { usePathname } from 'next/navigation'; | ||
|
|
||
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | ||
| import { CloseIcon } from '@/components/ui/Icon'; | ||
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; |
There was a problem hiding this comment.
[MEDIUM] Import 그룹 사이 빈 줄 누락
코드 스타일 규칙에 따르면 import 그룹 사이에 빈 줄이 필요합니다:
- React / Next.js (
react,next/*) - (빈 줄)
- 내부 모듈 (
@/...)
| import { useState, useSyncExternalStore } from 'react'; | |
| import { usePathname } from 'next/navigation'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { CloseIcon } from '@/components/ui/Icon'; | |
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; | |
| import { useState, useSyncExternalStore } from 'react'; | |
| import { usePathname } from 'next/navigation'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { CloseIcon } from '@/components/ui/Icon'; | |
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; |
| import { useEffect, useState } from 'react'; | ||
|
|
||
| import { useIsMounted } from '@frontend-toolkit-js/hooks'; | ||
|
|
||
| import { useRegisterFcmToken, useUnregisterFcmToken } from '@/api/notifications/queries'; | ||
| import { useFCMToken } from '@/hooks/useFCMToken'; | ||
| import { PUSH_NOTIFICATION_ENABLED_KEY, PUSH_NOTIFICATION_TOKEN_KEY } from '@/lib/pushNotification'; |
There was a problem hiding this comment.
[MEDIUM] Import 그룹 사이 빈 줄 누락
코드 스타일 규칙에 따르면 import 그룹 사이에 빈 줄이 필요합니다:
- React / 외부 라이브러리
- (빈 줄)
- 내부 모듈 (
@/...)
| import { useEffect, useState } from 'react'; | |
| import { useIsMounted } from '@frontend-toolkit-js/hooks'; | |
| import { useRegisterFcmToken, useUnregisterFcmToken } from '@/api/notifications/queries'; | |
| import { useFCMToken } from '@/hooks/useFCMToken'; | |
| import { PUSH_NOTIFICATION_ENABLED_KEY, PUSH_NOTIFICATION_TOKEN_KEY } from '@/lib/pushNotification'; | |
| import { useEffect, useState } from 'react'; | |
| import { useIsMounted } from '@frontend-toolkit-js/hooks'; | |
| import { useRegisterFcmToken, useUnregisterFcmToken } from '@/api/notifications/queries'; | |
| import { useFCMToken } from '@/hooks/useFCMToken'; | |
| import { PUSH_NOTIFICATION_ENABLED_KEY, PUSH_NOTIFICATION_TOKEN_KEY } from '@/lib/pushNotification'; |
| import { useEffect } from 'react'; | ||
|
|
||
| import { AlarmIcon, AlarmOnIcon } from '@/components/ui/Icon'; | ||
| import { useToastStore } from '@/components/ui/Toast/useToastStore'; | ||
| import { Toggle } from '@/components/ui/Toggle'; | ||
| import { usePushNotificationSetting } from '@/hooks/usePushNotificationSetting'; |
There was a problem hiding this comment.
[MEDIUM] Import 그룹 사이 빈 줄 누락
코드 스타일 규칙에 따르면 import 그룹 사이에 빈 줄이 필요합니다:
- React
- (빈 줄)
- 내부 모듈 (
@/...)
| import { useEffect } from 'react'; | |
| import { AlarmIcon, AlarmOnIcon } from '@/components/ui/Icon'; | |
| import { useToastStore } from '@/components/ui/Toast/useToastStore'; | |
| import { Toggle } from '@/components/ui/Toggle'; | |
| import { usePushNotificationSetting } from '@/hooks/usePushNotificationSetting'; | |
| import { useEffect } from 'react'; | |
| import { AlarmIcon, AlarmOnIcon } from '@/components/ui/Icon'; | |
| import { useToastStore } from '@/components/ui/Toast/useToastStore'; | |
| import { Toggle } from '@/components/ui/Toggle'; | |
| import { usePushNotificationSetting } from '@/hooks/usePushNotificationSetting'; |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | ||
| import { CloseIcon, ArrowIcon } from '@/components/ui/Icon'; | ||
| import { PWAInstallButton } from '@/components/PWAInstallButton'; | ||
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; |
There was a problem hiding this comment.
[MEDIUM] 상대 경로 import가 내부 모듈과 섞여 있음
코드 스타일 규칙에 따르면 상대 경로 import(./, ../)는 내부 모듈(@/...) import 뒤에 별도 그룹으로 분리되어야 합니다.
현재 lines 8-9의 상대 경로 import가 lines 10-15의 내부 모듈 import 사이에 위치하여 순서가 섞여 있습니다.
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; | |
| 'use client'; | |
| import { useEffect, useState } from 'react'; | |
| import Image from 'next/image'; | |
| import Link from 'next/link'; | |
| import { useRouter, usePathname } from 'next/navigation'; | |
| import { useAuth } from '@/hooks/useAuth'; | |
| import { useLogout } from '@/api/auth/queries'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { CloseIcon, ArrowIcon } from '@/components/ui/Icon'; | |
| import { PWAInstallButton } from '@/components/PWAInstallButton'; | |
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; | |
| import { AuthSection } from './AuthSection'; | |
| import { PWAInstallBanner } from './PWAInstallBanner'; |
| import { AuthSection } from './AuthSection'; | ||
| import { PWAInstallBanner } from './PWAInstallBanner'; | ||
| import { useAuth } from '@/hooks/useAuth'; | ||
| import { useLogout } from '@/api/auth/queries'; | ||
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | ||
| import { CloseIcon, ArrowIcon } from '@/components/ui/Icon'; | ||
| import { PWAInstallButton } from '@/components/PWAInstallButton'; | ||
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; |
There was a problem hiding this comment.
[MEDIUM] 상대 경로 import가 내부 모듈과 섞여 있음
코드 스타일 규칙에 따르면 상대 경로 import(./, ../)는 내부 모듈(@/...) import 뒤에 별도 그룹으로 분리되어야 합니다.
현재 lines 8-9의 상대 경로 import가 lines 10-15의 내부 모듈 import 사이에 위치하여 순서가 섞여 있습니다.
| import { AuthSection } from './AuthSection'; | |
| import { PWAInstallBanner } from './PWAInstallBanner'; | |
| import { useAuth } from '@/hooks/useAuth'; | |
| import { useLogout } from '@/api/auth/queries'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { CloseIcon, ArrowIcon } from '@/components/ui/Icon'; | |
| import { PWAInstallButton } from '@/components/PWAInstallButton'; | |
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; | |
| import { useAuth } from '@/hooks/useAuth'; | |
| import { useLogout } from '@/api/auth/queries'; | |
| import { usePWAInstall } from '@/hooks/usePWAInstall'; | |
| import { CloseIcon, ArrowIcon } from '@/components/ui/Icon'; | |
| import { PWAInstallButton } from '@/components/PWAInstallButton'; | |
| import { IOSInstallGuideBottomSheet } from '@/components/PWAInstallButton/IOSInstallGuideBottomSheet'; | |
| import { AuthSection } from './AuthSection'; | |
| import { PWAInstallBanner } from './PWAInstallBanner'; |
❓ 이슈
✍️ Description
PWA L1 — 커스텀 설치 프롬프트 UI 구현
src/hooks/usePWAInstall.ts (신규)
beforeinstallprompt이벤트 캡처 → deferredPrompt state 저장iOS 감지: useSyncExternalStore + navigator.userAgent (SSR safe)
display-mode: standalone 감지: 기존 useMediaQuery 재사용
promptInstall(): 브라우저 설치 프롬프트 실행, accepted 시 버튼 자동 숨김
src/components/PWAInstallButton/index.tsx (신규)
isInstallable || isIOS 가 false면 null 리턴 (설치 불가 환경, 이미 설치됨 포함)
iOS: 클릭 시 IOSInstallGuideBottomSheet 오픈
그 외: 클릭 시 promptInstall() 호출 → 브라우저 네이티브 프롬프트
className prop으로 배치 시 스타일 커스텀 가능
src/components/PWAInstallButton/IOSInstallGuideBottomSheet.tsx (신규)
iOS Safari는 beforeinstallprompt 미지원 → "공유 → 홈 화면에 추가" 3단계 안내
기존 BottomSheet 컴파운드 컴포넌트 재사용
📸 스크린샷 (UI 변경 시)
✅ Checklist
PR
Test
npm run build)npm run lint)Additional Notes