-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
아이폰용, 안드로이드용 어플 설치 여부를 홈 화면에서 보여주기 (feat.PWA) (#345)
* feat: (#342) 안드로이드용 화면 구현 * feat: (#342) 아이폰용 화면 구현 * feat: (#342) 모바일 안드로이드, IOS에서 접속했을 경우 하단에 설치를 해달라는 문구 보이도록 구현 * style: (#342) 볼더 탑 연하게 수정, 큰 화면에서도 자연스럽게 보이도록 수정 * 사이트를 웹앱으로 실행되도록 하고, meta, favicon 설정 (#320) * feat: (#319) favicon 및 디바이스별로 보여줄 보투게더 로고 이미지 적용 * feat: (#319) 모바일 즐겨찾기 후 사용시 웹앱 네이티브로 보이도록 선언 아이패드가 켜질 때 로딩중 화면 설정 * feat: (#319) 라인, 카카오톡 공유시 사이트 정보를 미리볼 수 있도록 설정 * feat: (#319) 모바일 환경에서 사이트에 접근할 경우 홈으로 즐겨찾기 여부를 묻는 기능 구현 * refactor: (#319) 모바일 사용자에게 즐겨찾기를 묻는 함수 코드 가독성 개선 * fix: (#319) 모바일 디바이스에 물어보도록 수정 * feat: (#310) PWA(프로그레시브 웹 앱) 요소를 충족시키는 조건 설정 * chore: (#319) 사용하지 않는 코드 삭제 * chore: (#319) EOL을 위한 개행 추가 * refactor: (#342) beforeinstallprompt 이벤트에 대한 타입 선언 및 적용 * refactor: (#342) 사용자에게 보여주는 문구 수정 및 사용하지 않는 코드 삭제
- Loading branch information
1 parent
c69ec15
commit 616674e
Showing
9 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions
30
...rc/components/common/AppInstallPrompt/MobileInstallPrompt/MobileInstallPrompt.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import MobileInstallPrompt from '.'; | ||
|
||
const meta: Meta<typeof MobileInstallPrompt> = { | ||
component: MobileInstallPrompt, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof MobileInstallPrompt>; | ||
|
||
export const Android: Story = { | ||
render: () => ( | ||
<MobileInstallPrompt | ||
platform="android" | ||
handleInstallClick={() => {}} | ||
handleCancelClick={() => {}} | ||
/> | ||
), | ||
}; | ||
|
||
export const Ios: Story = { | ||
render: () => ( | ||
<MobileInstallPrompt | ||
platform="ios" | ||
handleInstallClick={() => {}} | ||
handleCancelClick={() => {}} | ||
/> | ||
), | ||
}; |
49 changes: 49 additions & 0 deletions
49
frontend/src/components/common/AppInstallPrompt/MobileInstallPrompt/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import arrowUp from '@assets/arrow-up-on-square.svg'; | ||
import logo from '@assets/logo.svg'; | ||
import cancel from '@assets/x_mark_black.svg'; | ||
|
||
import * as S from './style'; | ||
|
||
interface MobileInstallPromptProps { | ||
platform: 'ios' | 'android'; | ||
handleInstallClick: () => void; | ||
handleCancelClick: () => void; | ||
} | ||
|
||
export default function MobileInstallPrompt({ | ||
platform, | ||
handleInstallClick, | ||
handleCancelClick, | ||
}: MobileInstallPromptProps) { | ||
return ( | ||
<S.Container> | ||
<S.Content> | ||
<S.Header> | ||
<S.LogoImage src={logo} alt="보투게더 로고 이미지" /> | ||
<S.HeaderContent> | ||
<S.HeaderTop> | ||
<S.Title>VoTogether</S.Title> | ||
<S.CancelButton onClick={handleCancelClick}> | ||
<S.IconImage src={cancel} alt="취소 아이콘" /> | ||
</S.CancelButton> | ||
</S.HeaderTop> | ||
<S.Description> | ||
VoTogether는 앱처럼 원활히 사용할 수 있습니다. 설치하시겠습니까? | ||
</S.Description> | ||
</S.HeaderContent> | ||
</S.Header> | ||
{platform === 'ios' && ( | ||
<S.IosContainer> | ||
<S.Description> | ||
브라우저 메뉴바에서 <S.IconImage src={arrowUp} alt="추가하기 아이콘" /> 모양 버튼을 | ||
눌러 "홈 화면에 추가하기"를 통해 설치를 할 수 있습니다. | ||
</S.Description> | ||
</S.IosContainer> | ||
)} | ||
{platform === 'android' && ( | ||
<S.InstallButton onClick={handleInstallClick}>홈 화면에 추가</S.InstallButton> | ||
)} | ||
</S.Content> | ||
</S.Container> | ||
); | ||
} |
107 changes: 107 additions & 0 deletions
107
frontend/src/components/common/AppInstallPrompt/MobileInstallPrompt/style.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { styled } from 'styled-components'; | ||
|
||
import { theme } from '@styles/theme'; | ||
|
||
export const Container = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: 100vw; | ||
border-top: 1px solid rgba(0, 0, 0, 0.3); | ||
position: fixed; | ||
bottom: 0; | ||
left: 0; | ||
background-color: white; | ||
z-index: ${theme.zIndex.modal}; | ||
`; | ||
|
||
export const Content = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
width: max-content; | ||
padding: 30px 20px; | ||
`; | ||
|
||
export const Header = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
margin-bottom: 50px; | ||
`; | ||
|
||
export const LogoImage = styled.img` | ||
border-radius: 16px; | ||
width: 80px; | ||
height: 80px; | ||
`; | ||
|
||
export const HeaderContent = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
margin-left: 24px; | ||
`; | ||
|
||
export const HeaderTop = styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
margin-bottom: 10px; | ||
`; | ||
|
||
export const Title = styled.span` | ||
font-size: 2.4rem; | ||
font-weight: 700; | ||
`; | ||
|
||
export const Description = styled.p` | ||
font-size: 1.6rem; | ||
font-weight: 700; | ||
`; | ||
|
||
export const CancelButton = styled.button` | ||
padding: 10px; | ||
position: relative; | ||
bottom: 10px; | ||
left: 10px; | ||
cursor: pointer; | ||
`; | ||
|
||
export const IconImage = styled.img` | ||
width: 24px; | ||
height: 24px; | ||
`; | ||
|
||
export const InstallButton = styled.button` | ||
align-self: end; | ||
border-radius: 6px; | ||
width: 190px; | ||
height: 40px; | ||
font-size: 1.6rem; | ||
font-weight: 500; | ||
color: white; | ||
background-color: #5383ed; | ||
cursor: pointer; | ||
`; | ||
|
||
export const IosContainer = styled.div` | ||
display: flex; | ||
align-items: center; | ||
align-self: end; | ||
gap: 8px; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Fragment, useEffect, useState } from 'react'; | ||
|
||
import { BeforeInstallPromptEvent } from '../../../../window'; | ||
|
||
import MobileInstallPrompt from './MobileInstallPrompt'; | ||
|
||
export default function AppInstallPrompt() { | ||
const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null); | ||
const isDeviceIOS = /iPad|iPhone|iPod/.test(window.navigator.userAgent); | ||
|
||
useEffect(() => { | ||
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt); | ||
|
||
return () => { | ||
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt); | ||
}; | ||
}, []); | ||
|
||
const handleBeforeInstallPrompt = (event: BeforeInstallPromptEvent) => { | ||
event.preventDefault(); | ||
setDeferredPrompt(event); | ||
}; | ||
|
||
const handleInstallClick = () => { | ||
if (deferredPrompt) { | ||
deferredPrompt.prompt(); | ||
|
||
deferredPrompt.userChoice.then(() => { | ||
setDeferredPrompt(null); | ||
}); | ||
} | ||
}; | ||
|
||
const handleCancelClick = () => { | ||
setDeferredPrompt(null); | ||
}; | ||
|
||
return ( | ||
<Fragment> | ||
{deferredPrompt && ( | ||
<MobileInstallPrompt | ||
handleInstallClick={handleInstallClick} | ||
handleCancelClick={handleCancelClick} | ||
platform={isDeviceIOS ? 'ios' : 'android'} | ||
/> | ||
)} | ||
</Fragment> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
import AppInstallPrompt from '@components/common/AppInstallPrompt'; | ||
import Layout from '@components/common/Layout'; | ||
import PostListPage from '@components/post/PostListPage'; | ||
|
||
export default function Home() { | ||
return ( | ||
<Layout isSidebarVisible={true}> | ||
<PostListPage /> | ||
<AppInstallPrompt /> | ||
</Layout> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
export interface BeforeInstallPromptEvent extends Event { | ||
readonly platforms: string[]; | ||
readonly userChoice: Promise<{ | ||
outcome: 'accepted' | 'dismissed'; | ||
platform: string; | ||
}>; | ||
prompt(): Promise<void>; | ||
} | ||
|
||
declare global { | ||
interface WindowEventMap { | ||
beforeinstallprompt: BeforeInstallPromptEvent; | ||
} | ||
} |