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 15 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
53 changes: 53 additions & 0 deletions src/@type/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,56 @@ 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 = CommonAlarm | FollowAlarm;
interface CommonAlarm {
id: number;
type: "COMMENT" | "LIKE_POST" | "MENTION_POST";
message: string;
agent: {
id: number;
username: string;
name: string;
image: {
imageUrl: string;
imageType: string;
imageName: string;
imageUUID: string;
live-small marked this conversation as resolved.
Show resolved Hide resolved
};
hasStory: false;
};
createdDate: string;
postId: number;
postImageUrl: string;
content: string;
mentionsOfContent: string[];
hashtagsOfContent: string[];
}

interface FollowAlarm {
id: number;
type: "FOLLOW";
message: string;
agent: {
id: number;
username: string;
name: string;
image: {
imageUrl: string;
imageType: string;
imageName: string;
imageUUID: string;
};
hasStory: false;
};
createdDate: string;
following: boolean;
}
live-small marked this conversation as resolved.
Show resolved Hide resolved
}
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);
}
});
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
41 changes: 37 additions & 4 deletions src/components/Common/Header/NavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ 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 +73,26 @@ const AvatarWrapper = styled(NavItemWrapper)<{ isSubnavModalOn: boolean }>`

const NavItems = () => {
const [isSubnavModalOn, setIsSubnavMoalOn] = useState(false);
const [isClickAlarm, setIsClickAlarm] = useState(false);

live-small marked this conversation as resolved.
Show resolved Hide resolved
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,
setIsClickAlarm,
alarmModalControllerRef,
);

const navItems = [
{
id: "홈",
Expand Down Expand Up @@ -127,8 +141,21 @@ const NavItems = () => {
{
id: "피드 활동",
path: "/",
component: <Heart />,
activeComponent: <HeartActive />,
component: (
<span ref={alarmModalControllerRef}>
<Heart onClick={() => setIsClickAlarm(!isClickAlarm)} />
</span>
),
activeComponent: (
<span ref={alarmModalControllerRef} style={{ width: `22px` }}>
<HeartActive
onClick={() => setIsClickAlarm(!isClickAlarm)}
/>
{isClickAlarm && (
<Alarm alarmContainerRef={alarmContainerRef} />
)}
</span>
),
},
];

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

<AvatarWrapper isSubnavModalOn={isSubnavModalOn}>
<div
ref={subModalControllerRef}
onClick={() => {
setIsSubnavMoalOn(!isSubnavModalOn);
}}
Expand All @@ -166,6 +198,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.CommonAlarm }) {
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>
);
}
51 changes: 51 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,51 @@
import AlarmProfile from "components/Common/Header/alarm/alarm_profile";
import { removeRefer } from "components/Common/Header/alarm/utils";
import FollowingModal from "components/Home/Modals/FollowingModal";
import useGapText from "hooks/useGapText";
import { Link } from "react-router-dom";
import styled from "styled-components";

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);

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>
{/* <button> */}
{alarm.following ? "팔로잉" : "팔로우"}
{/* {alarm.following ? <FollowingModal /> : <button>hi</button>} */}
{/* </button> */}
</Container>
live-small marked this conversation as resolved.
Show resolved Hide resolved
);
}
49 changes: 49 additions & 0 deletions src/components/Common/Header/alarm/alarm_list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import AlarmItem from "components/Common/Header/alarm/alarmType/alarm_item";
import FollowAlarm from "components/Common/Header/alarm/alarmType/follow_alarm";
import useOnView from "hooks/useOnView";
import { useEffect, useRef } from "react";
import styled from "styled-components";

const Container = styled.ul`
display: flex;
flex-direction: column;

.alarm-item {
padding: 12px 16px;
}
`;

export default function AlarmList({
alarmList,
onLoadExtraAlarm,
}: {
alarmList: Alarm.AlarmContent[];
onLoadExtraAlarm: () => void;
}) {
const lastAlarmItemRef = useRef<HTMLLIElement>(null);
const isVisible = useOnView(lastAlarmItemRef);

useEffect(() => {
isVisible && onLoadExtraAlarm();
}, [isVisible]);

return (
<Container>
{alarmList.map((alarm, index) => (
<li
className="alarm-item"
key={alarm.id}
ref={
index === alarmList.length - 4 ? lastAlarmItemRef : null
}
>
{alarm.type === "FOLLOW" ? (
<FollowAlarm alarm={alarm} />
) : (
<AlarmItem alarm={alarm} />
)}
</li>
))}
</Container>
);
}
Loading