-
Couldn't load subscription status.
- Fork 7
header alarm 구현완료 #102
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
base: develop
Are you sure you want to change the base?
header alarm 구현완료 #102
Changes from 21 commits
5df2417
8303eb7
b771cf5
d653f62
7b650e7
d5682fe
deb3f1c
48a352a
f00d848
766843e
5e067ed
9b72f22
f28db0c
c390cff
f987f73
c09bec6
020fd94
73790af
ef91700
cb8722a
0efa59a
d6e43f3
a62da14
54e36fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { createSlice } from "@reduxjs/toolkit"; | ||
| import { loadAlarmList } from "app/store/ducks/alarm/alarmThunk"; | ||
|
|
||
| export interface AlarmStateProps { | ||
| alarmList: Alarm.AlarmContent[] | null; | ||
| totalPage: number; | ||
| } | ||
|
|
||
| const initialState: AlarmStateProps = { | ||
| alarmList: null, | ||
| totalPage: 0, | ||
| }; | ||
|
|
||
| const alarmSlice = createSlice({ | ||
| name: " alarm", | ||
| initialState, | ||
| reducers: { | ||
| clearAlarmList: (state) => { | ||
| state.alarmList = null; | ||
| }, | ||
| }, | ||
| extraReducers: (build) => { | ||
| build // | ||
|
||
| .addCase(loadAlarmList.fulfilled, (state, action) => { | ||
| if (action.payload.currentPage === 1) { | ||
| state.alarmList = action.payload.content; | ||
| state.totalPage = action.payload.totalPages; | ||
| } else { | ||
| state.alarmList?.push(...action.payload.content); | ||
| } | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| export const alarmAction = alarmSlice.actions; | ||
|
|
||
| export const alarmReducer = alarmSlice.reducer; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { authorizedCustomAxios } from "customAxios"; | ||
| import { createAsyncThunk } from "@reduxjs/toolkit"; | ||
|
|
||
| export const loadAlarmList = createAsyncThunk< | ||
| Alarm.AlarmItem, | ||
| { page: number } | ||
| >("alarm/loadList", async (payload, ThunkOptions) => { | ||
| try { | ||
| const config = { | ||
| params: { | ||
| page: payload.page, | ||
| size: 10, | ||
| }, | ||
| }; | ||
| const { | ||
| data: { data }, | ||
| } = await authorizedCustomAxios.get(`/alarms`, config); | ||
| return { ...data, currentPage: payload.page }; | ||
| } catch (error) { | ||
| ThunkOptions.rejectWithValue(error); | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,20 +8,19 @@ import { ReactComponent as DirectActive } from "assets/Svgs/direct-active.svg"; | |
| import { ReactComponent as NewArticle } from "assets/Svgs/new-article.svg"; | ||
| import { ReactComponent as NewArticleActive } from "assets/Svgs/new-article-active.svg"; | ||
|
|
||
| import { ReactComponent as Map } from "assets/Svgs/map.svg"; | ||
| import { ReactComponent as MapActive } from "assets/Svgs/map-active.svg"; | ||
|
|
||
|
Comment on lines
-11
to
-13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 안쓰는 이미지라서 삭제했습니다 @kimyoungyin There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kimyoungyin 영인님 이 부분도 봐주신거 맞나용? |
||
| import { ReactComponent as Heart } from "assets/Svgs/heart.svg"; | ||
| import { ReactComponent as HeartActive } from "assets/Svgs/heart-active.svg"; | ||
|
|
||
| import { NavLink, Link } from "react-router-dom"; | ||
| import { NavLink } from "react-router-dom"; | ||
| import { useAppDispatch, useAppSelector } from "app/store/Hooks"; | ||
| import { selectView } from "app/store/ducks/direct/DirectSlice"; | ||
| import { uploadActions } from "app/store/ducks/upload/uploadSlice"; | ||
| import Upload from "components/Common/Header/Upload"; | ||
| import SubNav from "./SubNav"; | ||
|
|
||
| import { useRef, useState } from "react"; | ||
| import useOutsideClick from "hooks/useOutsideClick"; | ||
| import Alarm from "components/Common/Header/alarm"; | ||
|
|
||
| const Container = styled.div` | ||
| flex: 1 0 0%; | ||
|
|
@@ -71,14 +70,22 @@ const AvatarWrapper = styled(NavItemWrapper)<{ isSubnavModalOn: boolean }>` | |
|
|
||
| const NavItems = () => { | ||
| const [isSubnavModalOn, setIsSubnavMoalOn] = useState(false); | ||
| const [isAlarmOn, setIsAlarmOn] = useState(false); | ||
|
|
||
| const dispatch = useAppDispatch(); | ||
| const isUploading = useAppSelector(({ upload }) => upload.isUploading); | ||
| const userInfo = useAppSelector((state) => state.auth.userInfo); | ||
|
|
||
| // setting | ||
| const navContainerRef = useRef<HTMLDivElement | null>(null); | ||
| const subModalControllerRef = useRef<HTMLDivElement | null>(null); | ||
| const subModalControllerRef = useRef<HTMLImageElement | null>(null); | ||
| useOutsideClick(navContainerRef, setIsSubnavMoalOn, subModalControllerRef); | ||
|
|
||
| // alarm | ||
| const alarmContainerRef = useRef<HTMLDivElement | null>(null); | ||
| const alarmModalControllerRef = useRef<HTMLSpanElement | null>(null); | ||
| useOutsideClick(alarmContainerRef, setIsAlarmOn, alarmModalControllerRef); | ||
|
|
||
|
Comment on lines
+85
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 두 Ref는 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네네 맞아요 |
||
| const navItems = [ | ||
| { | ||
| id: "홈", | ||
|
|
@@ -127,8 +134,19 @@ const NavItems = () => { | |
| { | ||
| id: "피드 활동", | ||
| path: "/", | ||
| component: <Heart />, | ||
| activeComponent: <HeartActive />, | ||
| component: ( | ||
| <span ref={alarmModalControllerRef}> | ||
| <Heart onClick={() => setIsAlarmOn(!isAlarmOn)} /> | ||
| </span> | ||
| ), | ||
| activeComponent: ( | ||
| <span ref={alarmModalControllerRef} style={{ width: `22px` }}> | ||
| <HeartActive onClick={() => setIsAlarmOn(!isAlarmOn)} /> | ||
| {isAlarmOn && ( | ||
| <Alarm alarmContainerRef={alarmContainerRef} /> | ||
| )} | ||
| </span> | ||
| ), | ||
| }, | ||
| ]; | ||
|
|
||
|
|
@@ -144,6 +162,12 @@ const NavItems = () => { | |
| ? navItem.activeComponent | ||
| : navItem.component} | ||
| </div> | ||
| ) : navItem.id === "피드 활동" ? ( | ||
| <> | ||
| {isAlarmOn | ||
| ? navItem.activeComponent | ||
| : navItem.component} | ||
| </> | ||
| ) : ( | ||
| <NavLink to={navItem.path}> | ||
| {navItem.component} | ||
|
|
@@ -154,7 +178,6 @@ const NavItems = () => { | |
|
|
||
| <AvatarWrapper isSubnavModalOn={isSubnavModalOn}> | ||
| <div | ||
| ref={subModalControllerRef} | ||
| onClick={() => { | ||
| setIsSubnavMoalOn(!isSubnavModalOn); | ||
| }} | ||
|
|
@@ -166,6 +189,7 @@ const NavItems = () => { | |
| data-testid="user-avatar" | ||
| draggable="false" | ||
| src={userInfo?.memberImageUrl} | ||
| ref={subModalControllerRef} | ||
| /> | ||
| </div> | ||
| {isSubnavModalOn && ( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import React from "react"; | ||
| import styled from "styled-components"; | ||
| import AlarmProfile from "components/Common/Header/alarm/alarm_profile"; | ||
| import { Link } from "react-router-dom"; | ||
| import useGapText from "hooks/useGapText"; | ||
| import { removeRefer } from "components/Common/Header/alarm/utils"; | ||
| import StringFragmentWithMentionOrHashtagLink from "components/Common/StringFragmentWithMentionOrHashtagLink"; | ||
|
|
||
| const Container = styled.div` | ||
| display: flex; | ||
| flex: 1; | ||
|
|
||
| .alarm { | ||
| margin: 0 12px; | ||
| flex: 1; | ||
|
|
||
| a { | ||
| text-decoration: none; | ||
|
|
||
| .username { | ||
| font-weight: 700; | ||
| } | ||
| } | ||
|
|
||
| .create-date { | ||
| color: ${(props) => props.theme.font.gray}; | ||
| } | ||
| } | ||
|
|
||
| .relative-image { | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
|
|
||
| img { | ||
| height: 40px; | ||
| width: 40px; | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| export default function AlarmItem({ alarm }: { alarm: Alarm.PostAlarm }) { | ||
| const alarmMessage = removeRefer(alarm.message); | ||
| // 무한스크롤 | ||
| // 컴포넌트 언마운트 -> alarm창 닫도록 | ||
|
|
||
| return ( | ||
| <Container> | ||
| <AlarmProfile agent={alarm.agent} /> | ||
| <div className="alarm"> | ||
| <Link to={`/profile/${alarm.agent.username}`}> | ||
| <span className="username">{alarm.agent.username}</span> | ||
| </Link> | ||
| {alarmMessage} | ||
| <StringFragmentWithMentionOrHashtagLink | ||
| str={alarm.content} | ||
| mentions={alarm.mentionsOfContent} | ||
| hashtags={alarm.hashtagsOfContent} | ||
| />{" "} | ||
| <span className="create-date"> | ||
| {useGapText(alarm.createdDate)} | ||
| </span> | ||
| </div> | ||
| <div className="relative-image"> | ||
| <Link to={`/p/${alarm.postId}`}> | ||
| <img src={alarm.postImageUrl} alt={"이미지 썸네일"}></img> | ||
| </Link> | ||
| </div> | ||
| </Container> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { postFollow } from "app/store/ducks/home/homThunk"; | ||
| import { modalActions } from "app/store/ducks/modal/modalSlice"; | ||
| import { useAppDispatch } from "app/store/Hooks"; | ||
| import AlarmProfile from "components/Common/Header/alarm/alarm_profile"; | ||
| import { removeRefer } from "components/Common/Header/alarm/utils"; | ||
| import useGapText from "hooks/useGapText"; | ||
| import { useState } from "react"; | ||
| import { Link } from "react-router-dom"; | ||
| import styled, { useTheme } from "styled-components"; | ||
| import Button from "styles/UI/Button"; | ||
|
|
||
| const Container = styled.div` | ||
| display: flex; | ||
| flex: 1; | ||
| .alarm { | ||
| margin: 0 12px; | ||
| flex: 1; | ||
| a { | ||
| text-decoration: none; | ||
| .username { | ||
| font-weight: 700; | ||
| } | ||
| } | ||
| .create-date { | ||
| color: ${(props) => props.theme.font.gray}; | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| export default function FollowAlarm({ alarm }: { alarm: Alarm.FollowAlarm }) { | ||
| const alarmMessage = removeRefer(alarm.message); | ||
| const dispatch = useAppDispatch(); | ||
| const theme = useTheme(); | ||
| const [isFollowing, setIsFollowing] = useState(alarm.following); | ||
|
|
||
| const followHandler = () => { | ||
| dispatch(postFollow({ username: alarm.agent.username })) // | ||
| .then(() => { | ||
| setIsFollowing(!isFollowing); | ||
| }); | ||
| }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 에러가 발생했을 때는 어떻게 처리할까요? 아래 버튼 관련해서 개선하면 좋을 것 같아요. 참고로 |
||
|
|
||
| const showUnfollowingModalOnHandler = () => { | ||
live-small marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // 모달에 들어갈 유저 정보 세팅 | ||
| dispatch( | ||
| modalActions.setModalUsernameAndImageUrl({ | ||
| nickname: alarm.agent.username, | ||
| imageUrl: alarm.agent.image.imageUrl, | ||
| }), | ||
| ); | ||
| // 모달 켜기 | ||
| dispatch(modalActions.changeActivatedModal("alarmUnfollowing")); | ||
| }; | ||
|
|
||
| return ( | ||
| <Container> | ||
| <AlarmProfile agent={alarm.agent} /> | ||
| <div className="alarm"> | ||
| <Link to={`/profile/${alarm.agent.username}`}> | ||
| <span className="username">{alarm.agent.username}</span> | ||
| </Link> | ||
| {alarmMessage}{" "} | ||
| <span className="create-date"> | ||
| {useGapText(alarm.createdDate)} | ||
| </span> | ||
| </div> | ||
| {isFollowing ? ( | ||
| <Button | ||
| bgColor={theme.color.bg_gray} | ||
| color="black" | ||
| style={{ | ||
| border: `1px solid ${theme.color.bd_gray}`, | ||
| height: "30px", | ||
| }} | ||
| onClick={showUnfollowingModalOnHandler} | ||
| > | ||
| 팔로잉 | ||
| </Button> | ||
| ) : ( | ||
| <Button onClick={followHandler} style={{ height: "30px" }}> | ||
| 팔로우 | ||
| </Button> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 팔로우 요청 중일 때 로딩 circle이 필요할 것 같은데 관련 state를 추가해야 할 것 같은데 어떠세요? |
||
| )} | ||
| </Container> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.