Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: toggle UI/UX #491

Merged
merged 20 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7706d74
feat: Spinner ์ƒ์„ฑ
hozzijeong Nov 3, 2023
67e3578
refactor: getToken์ธ ๊ฒฝ์šฐ์— spinner ์ ์šฉ
hozzijeong Nov 3, 2023
c8371ae
chore: ์ค‘๋ณต ๋ฐ ํ•„์š”์—†๋Š” ์ฝ”๋“œ ์ œ๊ฑฐ
hozzijeong Nov 3, 2023
2695411
refactor: firefox์—์„œ๋„ ์•ฑ ์„ค์น˜ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ • ๋ฐ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•ˆ๋‚ด ์ฐฝ ํ™•์ธ ํ•˜๋„๋ก ์ˆ˜์ •
hozzijeong Nov 11, 2023
6dcef5d
design: ์›น์œผ๋กœ ๋ณด๊ธฐ ์ถ”๊ฐ€ ๋ฐ ๋””์ž์ธ ์ˆ˜์ •
hozzijeong Nov 11, 2023
b44ecd3
refactor: ๋กœ๊ทธ์ธ ํ›„ ์ฒ˜์Œ ๋ฆฌ๋งˆ์ธ๋” ์ ‘์†์‹œ์— ์•Œ๋ฆผ ํ”„๋กฌํ”„ํŠธ ๋„์šฐ๊ธฐ ์„ค์ •
hozzijeong Nov 11, 2023
ff6a05e
refactor: Toggle UI ์—…๋ฐ์ดํŠธ
hozzijeong Dec 8, 2023
97aaeed
feat: ํ† ํฐ ๊ด€๋ฆฌ query๋กœ ์ „์ด
hozzijeong Dec 13, 2023
bf91ec2
refactor: FCMMessaging ํด๋ž˜์Šค ์ˆ˜์ •
hozzijeong Dec 13, 2023
25f13aa
refactor: token ๋ฐ›์ง€ ์•Š๋„๋ก ์„ค์ •
hozzijeong Dec 13, 2023
eff0037
refactor: ํ† ํฐ ๊ด€๋ฆฌ hooks๋กœ ๋ถ„๋ฆฌ
hozzijeong Dec 13, 2023
a3dae30
refactor:deletetoken ์„ค์ • ์ถ”๊ฐ€
hozzijeong Dec 13, 2023
ae1a130
refactor: refetch ๊ธฐ์ค€ ์™„ํ™”
hozzijeong Dec 13, 2023
2527f6e
chore: exactOptionalPropertyTypes ์„ค์ • ์ œ๊ฑฐ
hozzijeong Jan 11, 2024
f283489
refactor: mock๋ฐ์ดํ„ฐ ํ™•๋ฅ  ์„ค์ •
hozzijeong Jan 11, 2024
40d2625
refactor: ์—๋Ÿฌ ๊ฐ์ฒด ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ๋” ๋ณ€๊ฒจ
hozzijeong Jan 11, 2024
e2cd969
refactor: global type ๋ถ„๋ฆฌ
hozzijeong Jan 11, 2024
c4a7426
chore: ๋ถˆํ•„์š”ํ•œ ์ฃผ์„ ๋ฐ console ์‚ญ์ œ
hozzijeong Jan 11, 2024
7af6f81
refactor: tsconfig Option์— ๋งž์ถฐ ์ฝ”๋“œ ๋ณ€๊ฒฝ
hozzijeong Jan 11, 2024
b9f7a82
refactor: ๊ตฌ๋… ์ทจ์†Œ์‹œ์— ์—๋Ÿฌ ํ•ธ๋“ค๋ง
hozzijeong Jan 11, 2024
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
3 changes: 1 addition & 2 deletions frontend/public/devLocalServiceWorker.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/* eslint-disable no-undef */
importScripts('/firebase-messaging-sw.js');
importScripts('/mockServiceWorker.js');
importScripts('/firebase-messaging-sw.js', '/mockServiceWorker.js');
5 changes: 1 addition & 4 deletions frontend/public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ const firebaseConfig = {
measurementId: 'G-8SL2D547VW',
};

firebase.initializeApp({
...firebaseConfig,
authDomain: 'pium-7ddfe.firebaseapp.com',
});
firebase.initializeApp(firebaseConfig);

// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
Expand Down
1 change: 0 additions & 1 deletion frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
media="screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/assets/android-icon-512x512.png"
/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

<meta property="og:type" content="website" />
<meta property="og:url" content="https://pium.life/" />
Expand Down
Binary file added frontend/src/assets/firefox_add_button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ export const Wrapper = styled.div`

display: flex;
flex-direction: column;
gap: 12px;
justify-content: space-evenly;

width: 100%;
min-width: ${({ theme }) => theme.width.mobile};
max-width: ${({ theme }) => theme.width.pad};
height: 120px;
margin: 16px 0;
padding: 0 32px;
padding: 16px 32px;

background: ${({ theme }) => theme.color.background};
border-bottom: solid 2px ${({ theme }) => theme.color.grayLight};
Expand All @@ -48,24 +48,28 @@ export const GuideParagraph = styled.p`
font: 500 1.6rem/2.2rem NanumSquareRound;
`;

export const IosGuide = styled.p`
export const Guide = styled.div`
display: flex;
flex-direction: column;
gap: 4px;
justify-content: center;
height: 30px;

font: 600 1.6rem/2.2rem NanumsquareRound;
`;

export const InfoButton = styled.button`
width: 100%;
height: 30px;
background-color: ${({ theme }) => theme.color.grayLight};
border-radius: 8px;
`;

export const ButtonWrapper = styled.div`
display: flex;
gap: 16px;
height: 30px;
font: 500 1.8rem/2.2rem NanumSquareRound;

button {
width: 100%;
background-color: ${({ theme }) => theme.color.grayLight};
border-radius: 8px;
}

:last-child {
color: ${({ theme }) => theme.color.background};
background-color: ${({ theme }) => theme.color.primary};
Expand Down
64 changes: 48 additions & 16 deletions frontend/src/components/@common/InstallPrompt/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
/* eslint-disable react/no-unescaped-entities */
import { useMemo } from 'react';
import {
ButtonWrapper,
ContentWrapper,
Guide,
GuideParagraph,
IosGuide,
InfoButton,
Wrapper,
} from './InstallPrompt.style';
import useInstallApp from 'hooks/@common/useInstallApp';
import theme from 'style/theme.style';
import FireFoxButton from 'assets/firefox_add_button.png';
import SvgFill from '../SvgIcons/SvgFill';

const InstallPrompt = () => {
const { installApp, ignoreInstallApp, showPrompt, closePrompt } = useInstallApp();

const isIos = /iPhone|iPod|iPad/i.test(navigator.userAgent);
const isFireFox = /Firefox/i.test(navigator.userAgent);

const promptGuide = useMemo(() => {
if (isIos) {
return (
<Guide>
<p>
ํ•˜๋‹จ ํƒญ์— ์žˆ๋Š” <SvgFill icon="ios-share" size={16} fill={theme.color.sub} /> ์•„์ด์ฝ˜์—์„œ
"ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€"๋ฅผ ํ†ตํ•ด ๋ฐ”๋กœ๊ฐ€๊ธฐ๋ฅผ ์„ค์น˜ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</p>
<InfoButton type="button" onClick={ignoreInstallApp}>
์›น์œผ๋กœ ๋ณผ๊ฒŒ์š”
</InfoButton>
</Guide>
);
}

// firefox์—์„œ๋Š” beforeinstallPrompt๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค
if (isFireFox) {
return (
<Guide>
<img src={FireFoxButton} width={210} height={30} alt="ํŒŒ์ด์–ดํญ์Šค ๋ฐ”๋กœ๊ฐ€๊ธฐ ์ถ”๊ฐ€" />
<p>
์ƒ๋‹จ ํƒญ์— ์žˆ๋Š” ๋”๋ณด๊ธฐ๋ฅผ ํด๋ฆญํ•œ ๋‹ค์Œ '์„ค์น˜' ๋ฒ„ํŠผ์„ ํ†ตํ•ด ๋ฐ”๋กœ๊ฐ€๊ธฐ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</p>
<InfoButton type="button" onClick={ignoreInstallApp}>
์›น์œผ๋กœ ๋ณผ๊ฒŒ์š”
</InfoButton>
</Guide>
);
}

return (
<ButtonWrapper>
<InfoButton type="button" onClick={ignoreInstallApp}>
์›น์œผ๋กœ ๋ณผ๊ฒŒ์š”
</InfoButton>
<InfoButton type="button" onClick={installApp}>
๋ฐ”๋กœ๊ฐ€๊ธฐ ์ถ”๊ฐ€
</InfoButton>
</ButtonWrapper>
);
}, [ignoreInstallApp, installApp, isFireFox, isIos]);

return (
<>
Expand All @@ -31,21 +77,7 @@ const InstallPrompt = () => {
๋ฐ”๋กœ๊ฐ€๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์‹œ๋ฉด ์•ฑ์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?
</GuideParagraph>
</ContentWrapper>
{isIos ? (
<IosGuide>
ํ•˜๋‹จ ํƒญ์— ์žˆ๋Š” <SvgFill icon="ios-share" size={16} fill={theme.color.sub} />{' '}
์•„์ด์ฝ˜์—์„œ "ํ™ˆ ํ™”๋ฉด์— ์ถ”๊ฐ€"๋ฅผ ํ†ตํ•ด ๋ฐ”๋กœ๊ฐ€๊ธฐ๋ฅผ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</IosGuide>
) : (
<ButtonWrapper>
<button type="button" onClick={ignoreInstallApp}>
์›น์œผ๋กœ ๋ณผ๊ฒŒ์š”
</button>
<button type="button" onClick={installApp}>
๋ฐ”๋กœ๊ฐ€๊ธฐ ์ถ”๊ฐ€
</button>
</ButtonWrapper>
)}
{promptGuide}
</Wrapper>
)}
</>
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/components/@common/Spinner/Spinner.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled, { keyframes } from 'styled-components';

const spinAnimation = keyframes`
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
`;

// ์Šคํ”ผ๋„ˆ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
export const Loader = styled.div<{ $size: string }>`
width: ${({ $size }) => $size}px;
height: ${({ $size }) => $size}px;
margin: 0 auto;

border: ${({ $size }) => Number($size) * 0.1}px solid rgba(255, 255, 255, 0.3);
border-top: ${({ $size }) => Number($size) * 0.1}px solid #0074d9;
border-radius: 50%;

animation: ${spinAnimation} 1s linear infinite;
`;

// UI ์ปจํ…Œ์ด๋„ˆ ์Šคํƒ€์ผ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
export const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
15 changes: 15 additions & 0 deletions frontend/src/components/@common/Spinner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Loader, Wrapper } from './Spinner.style';

interface SpinnerProps {
size?: string;
}

const Spinner = ({ size = '40' }: SpinnerProps) => {
return (
<Wrapper>
<Loader $size={size} />
</Wrapper>
);
};

export default Spinner;
3 changes: 2 additions & 1 deletion frontend/src/components/mypage/PushAlert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Suspense } from 'react';
import Spinner from 'components/@common/Spinner';
import { PushAlertContent, PushAlertWrapper, WarnParagraph } from './PushAlert.style';
import PushStatus from 'models/PushStatus';
import PushToggle from './PushToggle';
Expand All @@ -11,7 +12,7 @@ const PushAlert = () => {
<PushAlertWrapper>
<PushAlertContent>
<p>๋ฆฌ๋งˆ์ธ๋” ์•Œ๋ฆผ ๋ฐ›๊ธฐ</p>
<Suspense fallback={<div>๋กœ๋”ฉ์ค‘์ž‰ใ…‚๋‹ˆ๋‹ค?</div>}>
<Suspense fallback={<Spinner size="20" />}>
<PushToggle />
</Suspense>
</PushAlertContent>
Expand Down
52 changes: 23 additions & 29 deletions frontend/src/hooks/@common/useInstallApp.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,47 @@
import { useEffect, useState } from 'react';
import type { BeforeInstallPromptEvent } from 'types/global';
import { useCallback, useEffect, useState } from 'react';
import useCheckSessionId from 'hooks/queries/auth/useCheckSessionId';
import { getCookie, setCookie } from 'utils/cookie';
import { URL_PATH } from 'constants/index';

declare global {
interface WindowEventMap {
beforeinstallprompt: BeforeInstallPromptEvent;
}
}

interface BeforeInstallPromptEvent extends Event {
readonly platforms: Array<string>;
readonly userChoice: Promise<{
outcome: 'accepted' | 'dismissed';
platform: string;
}>;
prompt(): Promise<void>;
}
let deferredPrompt: BeforeInstallPromptEvent | null = null;

const useInstallApp = () => {
const { isSuccess: isLoggedIn } = useCheckSessionId(false);

const [deferredPrompt, setDeferredPrompt] = useState<BeforeInstallPromptEvent | null>(null);
const [showPrompt, setShowPrompt] = useState(JSON.parse(getCookie('PromptVisible') ?? 'true'));

const installApp = async () => {
if (!deferredPrompt) return;
await deferredPrompt.prompt();

await deferredPrompt.userChoice;

setDeferredPrompt(null);
deferredPrompt = null;
setShowPrompt(false);
};

const ignoreInstallApp = () => {
setCookie({ key: 'PromptVisible', value: 'false' });
setDeferredPrompt(null);
setCookie({ key: 'PromptVisible', value: 'false', path: URL_PATH.reminder });
deferredPrompt = null;
setShowPrompt(false);
};

const closePrompt = () => {
setDeferredPrompt(null);
deferredPrompt = null;
setShowPrompt(false);
};

const beforeInstallPromptHandler = (event: BeforeInstallPromptEvent) => {
event.preventDefault();
const showPrompt = JSON.parse(getCookie('PromptVisible') ?? 'true');

if (!showPrompt) return;
// TODO: ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ๊ฒฝ์šฐ์— ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๊ฒƒ์ธ๊ฐ€?
const beforeInstallPromptHandler = useCallback(
(event: BeforeInstallPromptEvent) => {
event.preventDefault();
if (!showPrompt) return;

setDeferredPrompt(event);
};
deferredPrompt = event;
},
[showPrompt]
);

// TODO: ์™œ '/'์—์„œ๋งŒ beforeinstallprompt๊ฐ€ ์ด๋ฒคํŠธ ์ถ”๊ฐ€๊ฐ€ ๋˜๋‚˜? prompt๊ฐ€ ๋‚˜์˜ค๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋‚˜์˜ค์ง€ ์•Š๋Š”๊ฐ€?
useEffect(() => {
Expand All @@ -56,9 +50,9 @@ const useInstallApp = () => {
return () => {
window.removeEventListener('beforeinstallprompt', beforeInstallPromptHandler);
};
}, []);
}, [beforeInstallPromptHandler]);

return { showPrompt: deferredPrompt && isLoggedIn, installApp, ignoreInstallApp, closePrompt };
return { showPrompt: showPrompt && isLoggedIn, installApp, ignoreInstallApp, closePrompt };
};

export default useInstallApp;
17 changes: 2 additions & 15 deletions frontend/src/hooks/@common/usePushAlert.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import useWebPush from 'hooks/queries/auth/useWebPush';
import FCMMessaging from 'models/FCMMessaging';
import PushStatus from 'models/PushStatus';
import useAddToast from './useAddToast';

Expand All @@ -13,28 +12,16 @@ const usePushAlert = () => {
return;
}

// subscribe๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด ํ•ด๋‹น ํ† ํฐ์„ ์ œ๊ฑฐํ•ด์•ผ ํ•œ๋‹ค.

const permission = await Notification.requestPermission();
PushStatus.setPermission(permission);

if (permission !== 'granted') {
addToast({ type: 'info', message: '์•Œ๋ฆผ์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค', time: 3000 });
return;
}
// TODO: ์‚ฌ์šฉ์ž๊ฐ€ ์ฒ˜์Œ ์•Œ๋ฆผ ์„ค์ •์„ ํ•œ ๊ฒƒ์ธ์ง€ ์•„๋‹ˆ๋ฉด ๊ธฐ์กด์—
try {
let token = PushStatus.getCurrentToken();

// ์ด์ค‘ throw... ์ด๊ฒŒ ๊ดœ์ฐฎ์€๊ฑธ๊นŒ?
if (token === null) {
// TODO: ์—ฌ๊ธฐ์„œ ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆผ;;
token = await FCMMessaging.getCurrentToken();

if (token === null) throw new Error();
}

subscribe(token);
try {
subscribe();
} catch (error) {
addToast({ type: 'error', message: '๊ตฌ๋…์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค', time: 3000 });
}
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/hooks/firebase/useGetToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import FCMMessaging from 'models/FCMMessaging';

const useGetToken = () =>
useSuspenseQuery({
queryKey: ['getFCMToken'],
queryFn: FCMMessaging.getCurrentToken,
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
});

export default useGetToken;
Loading