Skip to content

Commit

Permalink
[#125] feat: 알림 페이지 개발 (#126)
Browse files Browse the repository at this point in the history
* fix: fix cli notification page

* fix: fix chat list

* feat: add api for notifications

* feat: add notification card

* fix: fix err

* fix: fix eslint err

* chore: delete useles

* fix: fix lint

* feat: add mock data

* fix: fix switch case

* feat: add read path nofitication
  • Loading branch information
dohui-son authored Oct 1, 2024
1 parent ec71183 commit 7465e1e
Show file tree
Hide file tree
Showing 18 changed files with 508 additions and 7 deletions.
4 changes: 3 additions & 1 deletion packages/client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
"no-unused-vars": "warn", // no-unused-vars 규칙 추가
"@typescript-eslint/no-unused-vars": "warn", // @typescript-eslint/no-unused-vars 규칙 추가
"react-hooks/rules-of-hooks": "warn", // 이 규칙을 비활성화
"react/jsx-no-useless-fragment": "off" // 이 규칙을 비활성화
"react/jsx-no-useless-fragment": "off", // 이 규칙을 비활성화
"react-hooks/exhaustive-deps": "off", // react-hooks/exhaustive-deps 규칙 비활성화
"react/no-array-index-key": "off" // react/no-array-index-key 규칙 비활성화
},
"settings": {
"react": {
Expand Down
7 changes: 7 additions & 0 deletions packages/client/app/(withLayout)/notification/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ClientNotificationPage } from 'src/clientPages/notification'

function NotificationPage() {
return <ClientNotificationPage />
}

export default NotificationPage
37 changes: 37 additions & 0 deletions packages/client/src/clientPages/notification/api/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { APIApi, PageResponseNotificationResponse } from '@server-api/api'
import {
useMutation,
useQuery,
UseQueryOptions,
UseQueryResult,
} from '@tanstack/react-query'

import { config } from '@shared/api'

export const useFetchNotifications = (
dto: {
type: '전체' | '답변' | '채택' | '채팅'
page: number
size: number
},
options: Omit<UseQueryOptions<PageResponseNotificationResponse>, 'queryKey'>, // <PageResponseNotificationResponse>,
): UseQueryResult => {
return useQuery({
...options,
queryKey: [`/notifications`, dto.type, dto.page, dto.size],
queryFn: async () =>
(
await new APIApi(config).getNotificationsByMember(dto.type, {
page: dto.page,
size: dto.size,
})
).data,
})
}

export const usePatchNotification = () => {
return useMutation({
mutationFn: async (dto: { notificationId: number }) =>
(await new APIApi(config).readNotification(dto)).data,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// TODO: useInfiniteScrollNotifictaion
// 참고 https://oliveyoung.tech/blog/2023-10-04/useInfiniteQuery-scroll/

import { APIApi, NotificationResponse } from '@server-api/api'
import { config } from '@shared/api'
import { InfiniteData, useInfiniteQuery } from '@tanstack/react-query'
import { useEffect } from 'react'

interface InfinitNotification {
content: Array<NotificationResponse>
nextPage: number
hasNext?: boolean
}

// API 호출 함수

export const useInfiniteScrollNotifictaion = (
countPerPage: number,
): {
data: InfiniteData<InfinitNotification>
status: string
isFetchingNextPage: boolean
} => {
const fetchNotifications = async ({ pageParam = 0 }) => {
const response = await new APIApi(config).getNotificationsByMember(
'전체',
{
page: pageParam,
size: countPerPage,
},
)
const { data } = response
return {
content: data.content,
nextPage: pageParam + 1,
hasNext: data.hasNext || false,
}
}

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =
useInfiniteQuery({
queryKey: ['notifications'], // 쿼리 키
queryFn: fetchNotifications, // API 호출 함수
getNextPageParam: (lastPage: InfinitNotification) =>
lastPage.hasNext ? lastPage.nextPage : undefined, // 다음 페이지 번호 반환
})

// 스크롤 이벤트 핸들러
const handleScroll = (e) => {
if (!e.target.scrollingElement) {
return
}
const { scrollHeight, scrollTop, clientHeight } =
e.target.scrollingElement
if (
scrollHeight - scrollTop <= clientHeight * 1.5 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}

// 컴포넌트가 마운트될 때 스크롤 이벤트 리스너 추가
useEffect(() => {
document.addEventListener('scroll', handleScroll)
return () => {
document.removeEventListener('scroll', handleScroll)
}
}, [hasNextPage, isFetchingNextPage])
return {
data,
status,
isFetchingNextPage,
}
}
1 change: 1 addition & 0 deletions packages/client/src/clientPages/notification/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ClientNotificationPage } from './ui/ClientNotificationPage'
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use client'

import { NotificationCard } from '@entities/notification'
import { MainLoader } from '@shared/ui'
import { NotificationResponse } from '@server-api/api'
import { useInfiniteScrollNotifictaion } from '../hook/useInfiniteScrollNotifictaion'
import * as styles from './style.css'
import { usePatchNotification } from '../api/notification'

export function ClientNotificationPage() {
const { data, isFetchingNextPage, status } =
useInfiniteScrollNotifictaion(10)

const formatdNotification = (
notificationRes: NotificationResponse,
): { message: string; date: string } => {
const res = {
date: notificationRes.createdAt
? new Date(notificationRes.createdAt).toLocaleString()
: '-',
}

switch (notificationRes.type) {
case '답변':
return {
...res,
message: `"${notificationRes.triggerMemberNickName}"님이 답변을 남겼습니다.`,
}
case '채택':
return {
...res,
message: `"${notificationRes.triggerMemberNickName}"님이 답변을 채택했습니다. 크레딧이 적립됩니다.`,
}
case '채팅':
return {
...res,
message: `"${notificationRes.triggerMemberNickName}"님에게 채팅이 도착했습니다.`,
}
default:
return { ...res, message: `[ERROR] 잘못된 타입의 알림입니다.` }
}
}

const { mutate: patchNotification } = usePatchNotification()

return (
<div className={styles.container}>
<MainLoader loading={isFetchingNextPage} />
{status === 'success' &&
data.pages.map((page, pageIndex) => (
<div key={pageIndex}>
{page.content.map((notification) => {
const foramtted = formatdNotification(notification)
return (
<NotificationCard
onClick={() => {
if (notification.isRead) {
return
}
patchNotification(
{
notificationId: Number(
notification.notificationId,
),
},
{
onError: (error) => alert(error.message),
},
)
}}
key={notification.notificationId}
notifyMessage={foramtted.message}
date={foramtted.date}
isRead={notification.isRead || false}
/>
)
})}
</div>
))}
</div>
)
}
11 changes: 11 additions & 0 deletions packages/client/src/clientPages/notification/ui/style.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { style } from '@vanilla-extract/css'

export const container = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',

width: '100%',
gap: 8,
})
Loading

0 comments on commit 7465e1e

Please sign in to comment.