Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions toronto/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NotFoundPage,
Login,
SignUp,
NotificationsPage,
PostPage,
UserProfilePage,
EditProfilePage,
Expand All @@ -29,6 +30,7 @@ function App() {
<Route path='/create-post' element={<PostPage />} />
<Route path='/users/:userId' element={<UserProfilePage />} />
<Route path='/users/:userId/edit' element={<EditProfilePage />} />
<Route path='/notifications' element={<NotificationsPage />} />
<Route path='/login' element={<Login />} />
<Route path='/sign-up' element={<SignUp />} />
<Route
Expand Down
47 changes: 47 additions & 0 deletions toronto/src/api/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,53 @@ export const postLikeApi = async (postId) => {
});
return res;
};
export const getNotifications = async () => {
const token = getToken();
const res = await serverless({
method: 'GET',
url: '/notifications',
headers: {
Authorization: `Bearer ${token}`,
},
});
return res;
};

export const putNotificationsSeen = async () => {
const token = getToken();
const res = await serverless({
method: 'PUT',
url: '/notifications/seen',
headers: {
Authorization: `Bearer ${token}`,
},
});
return res;
};

export const postNotificationsCreate = async (
notificationType,
notificationTypeId,
userId,
postId,
) => {
if (!notificationType || !notificationTypeId || !userId || !postId) return;
const token = getToken();
const res = await serverless({
method: 'POST',
url: '/notifications/create',
body: {
notificationType,
notificationTypeId,
userId,
postId,
},
headers: {
Authorization: `Bearer ${token}`,
},
});
return res;
};

export const serverless = async (options) => {
try {
Expand Down
67 changes: 67 additions & 0 deletions toronto/src/components/atoms/Badge/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import styled from 'styled-components';

const BadgeContainer = styled.div`
position: relative;
display: inline-block;
`;

const Super = styled.sup`
position: absolute;
top: 0;
right: 0;
display: inline-flex;
align-items: center;
height: 20px;
padding: 0 8px;
font-size: 12px;
border-radius: 20px;
color: white;
border-radius: 20px;
background-color: #f44;
transform: translate(50%, -50%);

&.dot {
padding: 0;
width: 4px;
height: 4px;
border-radius: 50%;
}
`;

const Badge = ({
children,
count,
maxCount,
showZero,
dot = false,
backgroundColor,
textColor,
...props
}) => {
const colorStyle = {
backgroundColor,
color: textColor,
};
let badge = null;
if (count) {
badge = (
<Super style={colorStyle}>
{maxCount && count > maxCount ? `${maxCount}+ ` : count}
</Super>
);
} else {
if (count !== undefined) {
badge = showZero ? <Super style={colorStyle}>0</Super> : null;
} else if (dot) {
badge = <Super className='dot' style={colorStyle} />;
}
}
return (
<BadgeContainer {...props}>
{children}
{badge}
</BadgeContainer>
);
};

export default Badge;
21 changes: 21 additions & 0 deletions toronto/src/components/atoms/Skeleton/Notification.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styled from 'styled-components';
import Circle from './Circle';
import Paragraph from './Paragraph';

const Notification = () => {
return (
<Wrapper>
<Circle size={60}></Circle>
<Paragraph line={3} style={{ width: '300px' }}></Paragraph>
</Wrapper>
);
};

export default Notification;

const Wrapper = styled.div`
display: flex;
flex-direction: row;
gap: 0.5em;
margin: 0.5em 0 0.5em 0;
`;
3 changes: 2 additions & 1 deletion toronto/src/components/atoms/Skeleton/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Box from './Box';
import Circle from './Circle';
import Paragraph from './Paragraph';

import Notification from './Notification';
const Skeleton = {
Box,
Circle,
Paragraph,
Notification,
};

export default Skeleton;
1 change: 1 addition & 0 deletions toronto/src/components/atoms/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as Skeleton } from './Skeleton';
export { default as Spacer } from './Spacer';
export { default as StyledLink } from './StyledLink';
export { default as Text } from './Text';
export { default as Badge } from './Badge';
101 changes: 101 additions & 0 deletions toronto/src/components/molecules/Notification/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import styled from 'styled-components';
import { StyledLink, Avatar, Text } from '@/components/atoms';
import { putNotificationsSeen } from '@/api/Api.js';

const Notification = ({ notification, posts }) => {
const notificationPost = posts.filter(
(post) => post._id === notification.post,
)[0];
const title = JSON.parse(notificationPost.title).postTitle;
const handleClick = async () => {
await putNotificationsSeen();
};

if (notification.seen === false) {
return (
<Wrapper onClick={handleClick}>
<StyledLink to={`/controversy/result/${notification.post}`}>
<NotificationWrapper>
<AvatarWrapper>
<Avatar src={notification.author.image} size={60} />
</AvatarWrapper>
<ColWrapper>
<TextWrapper>
<Text color='#000'>
{notification.comment
? notification.author.username +
' 님이 ' +
title +
'에 댓글을 남겼습니다!'
: ''}
{notification.like
? notification.author.username +
' 님이 ' +
title +
'에 좋아요를 눌렀습니다!'
: ''}
</Text>
</TextWrapper>
<TextWrapper>
<Text color='#000' strong>
{notification.comment
? JSON.parse(notification.comment.comment).content
: ''}
</Text>
</TextWrapper>
<TextWrapper>
<Text color='#2f66d2'>
{notification.updatedAt.split('T')[0]}일
{' ' +
new Date(Date.parse(notification.updatedAt) + 3240 * 10000)
.toISOString()
.split('T')[1]
.slice(0, 5)}
분에 업데이트 되었습니다.
</Text>
</TextWrapper>
</ColWrapper>
</NotificationWrapper>
</StyledLink>
</Wrapper>
);
}
};

export default Notification;

const Wrapper = styled.div`
display: flex;
align-items: center;
padding: 1rem;
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
border-radius: 4px;
&:hover {
background-color: #eee;
}
`;

const NotificationWrapper = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`;

const AvatarWrapper = styled.div`
display: flex;
`;

const ColWrapper = styled.div`
display: flex;
justify-content: center;
flex-direction: column;
margin-left: 1rem;
`;

const TextWrapper = styled.div`
display: flex;
align-items: center;

word-break: keep-all;
line-height: 1.5em;
`;
1 change: 1 addition & 0 deletions toronto/src/components/molecules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as Upload } from './Upload';
export { default as DraggableArea } from './Upload/UploadArea';
export { default as UserItem } from './UserItem';
export { default as Vote } from './Vote';
export { default as Notification } from './Notification';
35 changes: 33 additions & 2 deletions toronto/src/components/organisms/NavigationBar/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
/* eslint-disable no-unused-vars */
import styled from 'styled-components';
import { useNavigate } from 'react-router-dom';
import { Icon, StyledLink, Image, Header, Text } from '@/components/atoms';
import {
Icon,
StyledLink,
Image,
Header,
Text,
Badge,
} from '@/components/atoms';

import { Tooltip } from '@/components/molecules';
import logoImg from '@/assets/images/toronto.png';
const NavigationBar = ({ user, handleLogout }) => {
Expand Down Expand Up @@ -29,6 +36,30 @@ const NavigationBar = ({ user, handleLogout }) => {
<Text>로그아웃</Text>
</NavigateLink>
</Tooltip>
<Tooltip text='알림'>
<StyledLink
to='/notifications'
style={{
color: 'inherit',
}}
>
{user.notifications.length === 0 ? (
Copy link
Member

Choose a reason for hiding this comment

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

이 부분에서 단순히 사용자가 받은 알림의 길이에 따라 뱃지를 보여줄지 말지를 결정하고 있는 것 같은데, 사용자 입장에서 받은 알림이 있지만 알림을 읽었을 때에도 뱃지가 떠 있는 건 UX 측면에서는 좋지 않은 것 같습니다! 해당 부분을 알림 배열의 seenfalse 인 값이 있을 때에만 뱃지를 출력하는 건 어떨까요?

<Icon
size={20}
iconName='bell'
style={{ verticalAlign: 'bottom' }}
/>
) : (
<Badge dot>
<Icon
size={20}
iconName='bell'
style={{ verticalAlign: 'bottom' }}
/>
</Badge>
)}
</StyledLink>
</Tooltip>
<Tooltip text='내 정보 보기'>
<NavigateLink to={`/users/${user._id}`}>
<Icon
Expand Down
18 changes: 18 additions & 0 deletions toronto/src/pages/ControversyResult.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
deleteLikeApi,
postCommentApi,
postLikeApi,
postNotificationsCreate,
} from '@/api/Api';

const ResultPage = () => {
Expand Down Expand Up @@ -155,6 +156,16 @@ const ResultPage = () => {
}),
postId: postId,
});

const commentId = res.data._id;
const commentUserId = data.author._id;
const commentPostId = res.data.post;
await postNotificationsCreate(
'COMMENT',
commentId,
commentUserId,
commentPostId,
);
setLoading({
...loading,
comment: false,
Expand Down Expand Up @@ -196,6 +207,13 @@ const ResultPage = () => {
...likeData,
isLiked: true,
});

const likeId = res.data._id;
const likePostId = res.data.post;
const likeUserId = data.author._id;

await postNotificationsCreate('LIKE', likeId, likeUserId, likePostId);

setLoading({
...loading,
like: false,
Expand Down
Loading