core/components/FCM.tsx
SKINMAKER 160fe4ecb3
feat: 투표 알림 기능 (#669)
* feat: ensure djs clients are singleton instances

* feat: add votes table

* feat: vote notification

* chore: reduce vote cooldown to 15 min

* feat: add SetNotification to server

* chore: add debug logs

* fix: do not add notification when token and voteid already exists

* feat: add loading indicator

* feat: refresh notification when voted

* feat: add opt-out

* feat: add debug log

* fix: initialize firebase app

* fix: remove app on messaging

* feat: show notifications only with service worker

* fix: state improperly used

* fix: schedule notification if notification is newly added

* chore:  remove duplicated notification

* chore: add spacing

* chore: get token if notification is granted

* chore: change vote cooldown to 12 hours

* chore: remove logging
2025-02-17 07:34:12 +09:00

166 lines
3.9 KiB
TypeScript

import Fetch from '@utils/Fetch'
import { initializeApp } from 'firebase/app'
import { getMessaging, getToken as getFirebaseToken } from 'firebase/messaging'
import { useState } from 'react'
import Button from './Button'
export async function getFCMToken() {
try {
const app = initializeApp({
apiKey: 'AIzaSyDWnwXCBaP1C627gfIBQxyZbmNnAU_b_1Q',
authDomain: 'koreanlist-e95d9.firebaseapp.com',
projectId: 'koreanlist-e95d9',
storageBucket: 'koreanlist-e95d9.firebasestorage.app',
messagingSenderId: '716438516411',
appId: '1:716438516411:web:cddd6c7cc3b0571fa4af9e',
})
const worker = await navigator.serviceWorker.register('/vote-notification-sw.js')
const messaging = getMessaging(app)
const token = await getFirebaseToken(messaging, {
vapidKey: process.env.NEXT_PUBLIC_FCM_VAPID_KEY,
serviceWorkerRegistration: worker,
})
return token
} catch (e) {
return null
}
}
function SetNotification({ id, notificationSet }: { id: string; notificationSet: boolean }) {
const [state, setState] = useState(notificationSet ? 1 : 0)
const [hold, setHold] = useState(false)
const getToken = async () => {
if (!('serviceWorker' in navigator)) {
setState(4)
return 'NO_SERVICE_WORKER'
}
if (!('Notification' in window)) {
setState(4)
return 'NO_NOTIFICATION'
}
const p = await Notification.requestPermission()
if (p !== 'granted') {
setState(5)
return 'PERMISSION_DENIED'
}
const token = await getFCMToken()
if (!token) {
setState(4)
return
}
const result = await Fetch('/users/notification', {
method: 'POST',
body: JSON.stringify({
token,
targetId: id,
}),
})
if (result.code === 200) {
setState(2)
} else {
setState(4)
}
}
const components = {
0: (
<>
<p className='whitespace-pre-line text-lg font-normal'>
12 .
</p>
<Button
disabled={hold}
onClick={() => {
setHold(true)
getToken()
.then(() => {
setHold(false)
})
.catch(() => {
setState(4)
})
}}
>
<>
<i className='far fa-bell' /> {hold ? '설정 중...' : '알림 설정'}
</>
</Button>
</>
),
1: (
<>
<p className='whitespace-pre-line text-lg font-normal'>
. .
</p>
<Button
disabled={hold}
onClick={() => {
setHold(true)
getFCMToken()
.then(async (token) => {
await Fetch('/users/notification', {
method: 'DELETE',
body: JSON.stringify({
token,
targetId: id,
}),
})
setHold(false)
setState(3)
})
.catch(() => {
setState(4)
})
}}
>
<>
<i className='far fa-bell-slash mr-1' /> {hold ? '설정 중...' : '알림 해제'}
</>
</Button>
</>
),
2: (
<>
<p className='whitespace-pre-line text-lg font-normal'> .</p>
</>
),
3: (
<>
<p className='whitespace-pre-line text-lg font-normal'> .</p>
</>
),
4: (
<>
<p className='whitespace-pre-line text-lg font-normal'>
. . {'\n'}
iOS Safari .
</p>
</>
),
5: (
<>
<p className='whitespace-pre-line text-lg font-normal'>
. .
</p>
</>
),
}
return (
components[state] ?? (
<p className='whitespace-pre-line text-lg font-normal'>
. .
</p>
)
)
}
export default SetNotification