Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/profile/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import NotFound from './components/Notfound';
import { ProfilePastShowsPage } from './pages/ProfilePastShowsPage';
import { ProfileVideosPage } from './pages/ProfileVideosPage';
import { ProfileLinkPage } from './pages/ProfileLinkPage';
import AppStoreBridge from './pages/AppStoreBridge';
import { Suspense } from 'react';

const App = () => {
Expand All @@ -23,6 +24,7 @@ const App = () => {
<BrowserRouter>
<Routes>
<Route path="/" element={<NotFound />} />
<Route path="/bridge/store" element={<AppStoreBridge />} />
<Route path="/:userCode" element={<ProfilePage />} />
<Route path="/:userCode/shows" element={<ProfilePastShowsPage />} />
<Route path="/:userCode/videos" element={<ProfileVideosPage />} />
Expand Down
8 changes: 8 additions & 0 deletions apps/profile/src/constants/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IS_PRODUCTION_PHASE } from './phase';

export const LINK = {
ANDROID_STORE: 'https://play.google.com/store/apps/details?id=com.nexters.boolti&hl=ko',
IOS_STORE: 'https://apps.apple.com/kr/app/%EB%B6%88%ED%8B%B0/id6476589322',
SHOW_DETAIL: (showId?: number) =>
`https://${IS_PRODUCTION_PHASE ? '' : 'dev.'}preview.boolti.in/show/${showId}`,
};
22 changes: 22 additions & 0 deletions apps/profile/src/constants/schemes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const BASE_SCHEME = 'boolti://';

interface SchemeOptions {
path: string;
query?: Record<string, string | number | boolean | undefined>;
}

export function createAppScheme({ path, query }: SchemeOptions): string {
const queryString = query
? '?' +
Object.entries(query)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
.join('&')
: '';
return `${BASE_SCHEME}${path}${queryString}`;
}

export const SCHEMES = {
홈: () => createAppScheme({ path: 'home' }),
선물_등록: (giftId: string) => createAppScheme({ path: `gift/${giftId}` }),
};
44 changes: 44 additions & 0 deletions apps/profile/src/pages/AppStoreBridge/AppStoreBridge.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import styled from '@emotion/styled';

const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
background-color: #f8f9fa;
`;

const LogoWrapper = styled.div`
margin-bottom: 32px;

svg {
width: 120px;
height: auto;
}
`;

const Title = styled.h1`
font-size: 24px;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 12px;
text-align: center;
`;

const Description = styled.p`
font-size: 16px;
color: #666;
text-align: center;
line-height: 1.5;
`;

const Styled = {
Container,
LogoWrapper,
Title,
Description,
};

export default Styled;
36 changes: 36 additions & 0 deletions apps/profile/src/pages/AppStoreBridge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect } from 'react';
import { navigateToAppScheme } from '~/utils/app';
import { openStoreLink } from '~/utils/link';
import { SCHEMES } from '~/constants/schemes';
import { BooltiGreyLogo } from '@boolti/icon';
import Styled from './AppStoreBridge.styles';

const AppStoreBridge = () => {
useEffect(() => {
const handleRedirect = async () => {
const success = await navigateToAppScheme(SCHEMES.홈());

if (!success) {
openStoreLink();
}
};

handleRedirect();
}, []);

return (
<Styled.Container>
<Styled.LogoWrapper>
<BooltiGreyLogo />
</Styled.LogoWrapper>
<Styled.Title>불티 앱으로 이동 중...</Styled.Title>
<Styled.Description>
앱이 설치되지 않은 경우
<br />
스토어로 이동합니다
</Styled.Description>
</Styled.Container>
);
};

export default AppStoreBridge;
11 changes: 4 additions & 7 deletions apps/profile/src/pages/ProfilePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,9 @@ const ProfilePage = () => {
setIsShareDropdownOpen(false);
};

const getStoreLink = () => {
const isAndroid = /android/i.test(navigator.userAgent);

return isAndroid
? 'https://play.google.com/store/apps/details?id=com.nexters.boolti&hl=ko'
: 'https://apps.apple.com/kr/app/불티/id6476589322';
const getBridgeLink = () => {
const url = `${PROFILE_URL}bridge/store`;
return url;
};

const reservationButtonClickHandler = (isDesktop: boolean) => {
Expand All @@ -100,7 +97,7 @@ const ProfilePage = () => {
<Styled.DialogContainer>
<Styled.DialogQRCodeContainer>
<Styled.QRCodeContainer>
<QRCodeSVG value={getStoreLink()} size={182} level="H" />
<QRCodeSVG value={getBridgeLink()} size={182} level="H" />
</Styled.QRCodeContainer>
<BooltiGreyLogo />
</Styled.DialogQRCodeContainer>
Expand Down
32 changes: 32 additions & 0 deletions apps/profile/src/utils/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const navigateToAppScheme = async (appScheme: string): Promise<boolean> => {
let timerId: ReturnType<typeof setTimeout>;

return new Promise((resolve) => {
const handleVisibilityChange = () => {
if (document.hidden) {
clearTimeout(timerId);
document.removeEventListener('visibilitychange', handleVisibilityChange);
resolve(true);
}
};

document.addEventListener('visibilitychange', handleVisibilityChange);

window.addEventListener(
'blur',
() => {
clearTimeout(timerId);
document.removeEventListener('visibilitychange', handleVisibilityChange);
resolve(true);
},
{ once: true },
);

window.location.href = appScheme;

timerId = setTimeout(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
resolve(false);
}, 1_000);
});
};
13 changes: 13 additions & 0 deletions apps/profile/src/utils/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { LINK } from '~/constants/link';

export const getStoreLink = (): string => {
const userAgent = window.navigator.userAgent.toLowerCase();

const isIOS = /iphone|ipad|ipod/.test(userAgent);

return isIOS ? LINK.IOS_STORE : LINK.ANDROID_STORE;
};

export const openStoreLink = (): void => {
window.open(getStoreLink(), '_blank');
};
Loading