-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
알림창, 마이페이지 드롭다운 공용 컴포넌트 구현 + 알림 기능 추가 (#613)
* refactor: 러너, 서포터 마이페이지 분리 * assets: 알림 아이콘 에셋 추가 * feat: Dropdown 공용 컴포넌트 구현 * feat: 알림 컴포넌트 구현 * design: 드롭다운 컴포넌트 z-index 추가 * assets: 마이페이지, 로그아웃 아이콘 에셋 추가 * feat: 드롭다운 메뉴와 트리거와의 gap 설정 props 추가 * design: 마지막 li border-radius 추가 * feat: 드롭다운 컴포넌트 esc, backdrop 클릭 닫기 구현 * feat: 러너, 서포터 마이페이지 라우터 추가 * design: 알림창 반응형 추가 * feat: 프로필 드롭다운 컴포넌트 구현 * feat: 알림, 프로필 클릭 시 드롭다운 되도록 변경 * refactor: button 태그를 p태그로 변경 * fix: 메인 페이지 이외에서 로그아웃시 권한오류 뜨는 것 수정 * chore: cypress open 스크립트 추가 * test: 러너, 서포터 마이페이지 렌더링 테스트 코드 작성 * feat: 드롭다운 컴포넌트 스토리북 추가 * fix: 글 생성 완료 페이지에서 러너 마이페이지로 가도록 변경 * fix: ci 테스트 실패 오류 수정 (명령어 체인 분리) * design: 버튼 기본 스타일 제거 * design: 삭제 및 스크롤 추가 * feat: 알림 타입 및 게시물로 이동 이벤트 추가 * fix: 분리된 마이페이지로 라우팅 변경 * feat: 러너, 서포터 마이페이지 리액트 쿼리 마이그레이션 * style: 불필요한 공백 삭제 * refactor: Notification -> Alarm으로 네이밍 변경 * feat: 알람 불러오기 및 삭제 기능 추가 * feat: 알림 읽음 patch 구현 * feat: 알람이 없는 경우 빈 알람 아이콘 추가 * feat: 회원탈퇴 버튼 추가 * fix: 아이콘 오타 수정 * refactor: notification으로 네이밍 변경 * refactor: 콜백함수 제거 및 네이밍 변경 * refactor: 불필요한 async 제거 * refactor: 알람 인자로 isLogin 추가 * design: 알람 읽음 표시 추가 * refactor: isLogin 인자 제거
- Loading branch information
Showing
32 changed files
with
968 additions
and
209 deletions.
There are no files selected for viewing
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
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
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.
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.
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
161 changes: 161 additions & 0 deletions
161
frontend/src/components/NotificationDropdown/NotificationDropdown.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,161 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import { Notification } from '@/types/notification'; | ||
import { usePageRouter } from '@/hooks/usePageRouter'; | ||
import { useNotificationDelete } from '@/hooks/query/useNotificationDelete'; | ||
import { useNotificationCheck } from '@/hooks/query/useNotificationCheck'; | ||
|
||
interface Props { | ||
notificationList: Notification[]; | ||
} | ||
|
||
const NotificationDropdown = ({ notificationList }: Props) => { | ||
const { mutate: deleteNotification } = useNotificationDelete(); | ||
const { mutate: patchNotificationCheck } = useNotificationCheck(); | ||
const { goToRunnerPostPage } = usePageRouter(); | ||
|
||
const handlePostClick = (notificationId: number, runnerPostId: number) => { | ||
patchNotificationCheck(notificationId); | ||
goToRunnerPostPage(runnerPostId); | ||
}; | ||
|
||
const handleDeleteNotification = (e: React.MouseEvent, notificationId: number) => { | ||
e.stopPropagation(); | ||
|
||
deleteNotification(notificationId); | ||
}; | ||
|
||
return ( | ||
<S.DropdownContainer> | ||
{notificationList?.length > 0 ? ( | ||
notificationList?.map((notification) => { | ||
return ( | ||
<S.DropdownList | ||
key={notification.notificationId} | ||
onClick={() => handlePostClick(notification.notificationId, notification.referencedId)} | ||
> | ||
<S.NotificationTitleContainer> | ||
<S.NotificationTitle isRead={notification.isRead}>{notification.title}</S.NotificationTitle> | ||
<S.CloseButton onClick={(e) => handleDeleteNotification(e, notification.notificationId)}> | ||
삭제 | ||
</S.CloseButton> | ||
</S.NotificationTitleContainer> | ||
<S.NotificationContents>{notification.message}</S.NotificationContents> | ||
<S.NotificationTime>{notification.createdAt}</S.NotificationTime> | ||
</S.DropdownList> | ||
); | ||
}) | ||
) : ( | ||
<S.EmptyMessage>새로운 알림이 없습니다.</S.EmptyMessage> | ||
)} | ||
</S.DropdownContainer> | ||
); | ||
}; | ||
|
||
export default NotificationDropdown; | ||
|
||
const S = { | ||
DropdownContainer: styled.ul` | ||
width: 414px; | ||
max-height: 427px; | ||
overflow-y: scroll; | ||
& > li { | ||
border-bottom: 1px solid var(--gray-400); | ||
} | ||
& > li:last-child { | ||
border-bottom: none; | ||
border-radius: 0 0 10px 10px; | ||
} | ||
@media (max-width: 768px) { | ||
width: 290px; | ||
} | ||
`, | ||
|
||
DropdownList: styled.li` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 12px; | ||
padding: 20px 35px; | ||
cursor: pointer; | ||
&:hover { | ||
background-color: var(--gray-100); | ||
} | ||
`, | ||
|
||
NotificationTitleContainer: styled.div` | ||
display: flex; | ||
justify-content: space-between; | ||
`, | ||
|
||
CloseButton: styled.button` | ||
font-size: 12px; | ||
&:hover { | ||
color: var(--baton-red); | ||
} | ||
@media (max-width: 768px) { | ||
font-size: 10px; | ||
} | ||
`, | ||
|
||
NotificationTitle: styled.p<{ isRead: boolean }>` | ||
font-size: 16px; | ||
font-weight: 700; | ||
position: relative; | ||
${({ isRead }) => | ||
isRead | ||
? () => '' | ||
: ` &::before { | ||
width: 4px; | ||
height: 4px; | ||
content: ''; | ||
position: absolute; | ||
left: -10px; | ||
top: 50%; | ||
transform: translateY(-50%); | ||
background-color: var(--baton-red); | ||
border-radius: 50%; | ||
}`}; | ||
@media (max-width: 768px) { | ||
font-size: 14px; | ||
} | ||
`, | ||
|
||
NotificationContents: styled.p` | ||
font-size: 14px; | ||
color: var(--gray-700); | ||
@media (max-width: 768px) { | ||
font-size: 12px; | ||
} | ||
`, | ||
|
||
NotificationTime: styled.p` | ||
font-size: 12px; | ||
color: var(--gray-700); | ||
text-align: end; | ||
@media (max-width: 768px) { | ||
font-size: 10px; | ||
} | ||
`, | ||
|
||
EmptyMessage: styled.p` | ||
height: 427px; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
`, | ||
}; |
77 changes: 77 additions & 0 deletions
77
frontend/src/components/ProfileDropdown/ProfileDropdown.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,77 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
import MyPageIcon from '@/assets/my-page-icon.svg'; | ||
import LogoutIcon from '@/assets/logout-icon.svg'; | ||
import { useLogin } from '@/hooks/useLogin'; | ||
import { usePageRouter } from '@/hooks/usePageRouter'; | ||
|
||
const ProfileDropdown = () => { | ||
const { logout } = useLogin(); | ||
const { goToMainPage, goToRunnerMyPage, goToSupporterMyPage } = usePageRouter(); | ||
|
||
const handleClickRunnerMyPage = () => { | ||
goToRunnerMyPage(); | ||
}; | ||
|
||
const handleClickSupporterMyPage = () => { | ||
goToSupporterMyPage(); | ||
}; | ||
|
||
const handleClickLogoutButton = () => { | ||
logout(); | ||
|
||
goToMainPage(); | ||
window.location.reload(); | ||
}; | ||
|
||
return ( | ||
<S.DropdownContainer> | ||
<S.DropdownList aria-label="러너 마이페이지" onClick={handleClickRunnerMyPage}> | ||
<img width="18px" height="18px" src={MyPageIcon} /> | ||
<S.DropdownListTitle>러너 마이페이지</S.DropdownListTitle> | ||
</S.DropdownList> | ||
<S.DropdownList aria-label="서포터 마이페이지" onClick={handleClickSupporterMyPage}> | ||
<img width="18px" height="18px" src={MyPageIcon} /> | ||
<S.DropdownListTitle>서포터 마이페이지</S.DropdownListTitle> | ||
</S.DropdownList> | ||
<S.DropdownList aria-label="로그아웃" onClick={handleClickLogoutButton}> | ||
<img width="18px" height="18px" src={LogoutIcon} /> | ||
<S.DropdownListTitle>로그아웃</S.DropdownListTitle> | ||
</S.DropdownList> | ||
</S.DropdownContainer> | ||
); | ||
}; | ||
|
||
export default ProfileDropdown; | ||
|
||
const S = { | ||
DropdownContainer: styled.ul` | ||
width: max-content; | ||
height: max-content; | ||
& > li { | ||
border-bottom: 1px solid var(--gray-400); | ||
} | ||
& > li:last-child { | ||
border-bottom: none; | ||
border-radius: 0 0 10px 10px; | ||
} | ||
`, | ||
|
||
DropdownList: styled.li` | ||
display: flex; | ||
align-items: center; | ||
gap: 12px; | ||
padding: 15px 25px; | ||
cursor: pointer; | ||
&:hover { | ||
background-color: var(--gray-100); | ||
} | ||
`, | ||
|
||
DropdownListTitle: styled.p``, | ||
}; |
Oops, something went wrong.