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

header alarm 구현완료 #102

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5df2417
feat: 알람 컨테이너 UI 구현, 알람 리스트 thunk로 불러옴
live-small Aug 8, 2022
8303eb7
fix: alarm list 응답 타입, index.d.ts 파일에서 관리하도록 수정
live-small Aug 8, 2022
b771cf5
feat: 알람 아이템 > 알람보낸 프로필 UI 구현
live-small Aug 8, 2022
d653f62
feat: 알림 item 하나로 통일, 로딩처리, 알람없는 경우 처리
live-small Aug 10, 2022
7b650e7
feat: 알람 내 게시글 썸네일 클릭 시 해당 게시글로 이동
live-small Aug 10, 2022
d5682fe
fix: 알람 item UI 일부 수정
live-small Aug 10, 2022
deb3f1c
refactor: div 대신 ul,li 태그 이용
live-small Aug 11, 2022
48a352a
Merge branch 'develop' into feature/alarm
live-small Aug 30, 2022
f00d848
Merge branch 'develop' into feature/alarm
live-small Aug 30, 2022
766843e
feat: hashtag, mentions 링크,스타일링 적용
live-small Aug 30, 2022
5e067ed
fix: useOnView 수정: parameter, useEffect deps
live-small Sep 13, 2022
9b72f22
fix: Alarm 무한스크롤을 위해 Alarm type 수정
live-small Sep 13, 2022
f28db0c
feat: Alarm 무한스크롤 구현 - alarm list 컴포넌트에서 observing 붙임
live-small Sep 13, 2022
c390cff
fix: 불필요한 log 제거
live-small Sep 13, 2022
f987f73
Merge branch 'develop' into feature/alarm
live-small Sep 13, 2022
c09bec6
live-small Oct 3, 2022
020fd94
feat: 알람>팔로우, 팔로잉 버튼 UI, 모달 핸들러 구현
live-small Oct 3, 2022
73790af
fix: alarm type 중복되는 타입 묶어서 확장하는 방향으로 수정
live-small Oct 3, 2022
ef91700
feat: 알람 없을 때 UI 수정
live-small Oct 3, 2022
cb8722a
fix: 알람 모달상태값 isClickAlarm -> isAlarmOn
live-small Oct 3, 2022
0efa59a
fix: 안쓰는 컴포넌트 import 삭제
live-small Oct 3, 2022
d6e43f3
fix: 알람버튼 cursor pointer
live-small Oct 3, 2022
a62da14
fix: 공통 컨벤션으로 수정
live-small Dec 27, 2022
54e36fc
fix: Alarm 무한스크롤 구현 시 필요한 상태값, 컴포넌트에서 관리하도록 수정
live-small Dec 28, 2022
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
38 changes: 38 additions & 0 deletions src/@type/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ declare module ModalType {
| "report"
| "articleMenu"
| "shareWith"
| "alarmUnfollowing"
| null;

interface ModalPositionProps {
Expand Down Expand Up @@ -460,3 +461,40 @@ declare module EditType {

type modalType = "image" | "gender" | null;
}

declare module Alarm {
live-small marked this conversation as resolved.
Show resolved Hide resolved
type AlarmItem = {
content: AlarmContent[];
totalPages: number;
currentPage: number;
};
live-small marked this conversation as resolved.
Show resolved Hide resolved

type AlarmContent = PostAlarm | FollowAlarm;

interface CommonAlarm {
id: number;
type: "COMMENT" | "LIKE_POST" | "MENTION_POST";
message: string;
agent: {
id: number;
username: string;
name: string;
image: CommonType.ImageInfo;
hasStory: false;
};
createdDate: string;
}
interface PostAlarm extends CommonAlarm {
type: "COMMENT" | "LIKE_POST" | "MENTION_POST";
live-small marked this conversation as resolved.
Show resolved Hide resolved
postId: number;
postImageUrl: string;
content: string;
mentionsOfContent: string[];
hashtagsOfContent: string[];
}

interface FollowAlarm extends CommonAlarm {
type: "FOLLOW";
following: boolean;
}
}
37 changes: 37 additions & 0 deletions src/app/store/ducks/alarm/alarmSlice.ts
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 //
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 팔로우/언팔로우 관련 extraReducer 추가 예정인가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아뇨! 팔로우/언팔로우 api는 home thunk에 있는 로직 재사용했습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 넵 근데 promise 상태를 보여주려면 extraReducer가 필요할 수 있을지도 모르겠네요. 아래 리뷰 참고 부탁드립니다.

.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;
22 changes: 22 additions & 0 deletions src/app/store/ducks/alarm/alarmThunk.ts
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);
}
});
7 changes: 7 additions & 0 deletions src/app/store/ducks/modal/modalSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const modalSlice = createSlice({
name: "modal",
initialState,
reducers: {
setModalUsernameAndImageUrl: (
state,
action: PayloadAction<{ nickname: string; imageUrl: string }>,
) => {
state.memberNickname = action.payload.nickname;
state.memberImageUrl = action.payload.imageUrl;
},
startModal: (
state,
action: PayloadAction<ModalType.ModalStateProps>,
Expand Down
2 changes: 2 additions & 0 deletions src/app/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { alarmReducer } from "./ducks/alarm/alarmSlice";
import { configureStore } from "@reduxjs/toolkit";
import { authReducer } from "app/store/ducks/auth/authSlice";
import { homeReducer } from "app/store/ducks/home/homeSlice";
Expand All @@ -18,6 +19,7 @@ export const store = configureStore({
profile: profileReducer,
edit: editReducer,
common: commonReducer,
alarm: alarmReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
Expand Down
40 changes: 32 additions & 8 deletions src/components/Common/Header/NavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안쓰는 이미지라서 삭제했습니다 @kimyoungyin
아마 저희가 저 기능 구현안하기로해서 작업안한거 맞죠..?

Copy link
Member Author

Choose a reason for hiding this comment

The 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%;
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 두 Ref는 useOutsideClick을 위해 생성한 걸까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네 맞아요

const navItems = [
{
id: "홈",
Expand Down Expand Up @@ -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>
),
},
];

Expand All @@ -144,6 +162,12 @@ const NavItems = () => {
? navItem.activeComponent
: navItem.component}
</div>
) : navItem.id === "피드 활동" ? (
<>
{isAlarmOn
? navItem.activeComponent
: navItem.component}
</>
) : (
<NavLink to={navItem.path}>
{navItem.component}
Expand All @@ -154,7 +178,6 @@ const NavItems = () => {

<AvatarWrapper isSubnavModalOn={isSubnavModalOn}>
<div
ref={subModalControllerRef}
onClick={() => {
setIsSubnavMoalOn(!isSubnavModalOn);
}}
Expand All @@ -166,6 +189,7 @@ const NavItems = () => {
data-testid="user-avatar"
draggable="false"
src={userInfo?.memberImageUrl}
ref={subModalControllerRef}
/>
</div>
{isSubnavModalOn && (
Expand Down
71 changes: 71 additions & 0 deletions src/components/Common/Header/alarm/alarmType/alarm_item.tsx
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>
);
}
90 changes: 90 additions & 0 deletions src/components/Common/Header/alarm/alarmType/follow_alarm.tsx
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);
});
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러가 발생했을 때는 어떻게 처리할까요? 아래 버튼 관련해서 개선하면 좋을 것 같아요.

참고로 homeSlice에는 각 게시글에 followLoading state가 있긴 합니다


const showUnfollowingModalOnHandler = () => {
live-small marked this conversation as resolved.
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>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

팔로우 요청 중일 때 로딩 circle이 필요할 것 같은데 관련 state를 추가해야 할 것 같은데 어떠세요?
pending 상태가 아니라면 버튼을 비활성화 시키는 게 좋을 것 같습니다

)}
</Container>
);
}
Loading