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

[JR-845] 알림 구현 #165

Merged
merged 5 commits into from
Oct 8, 2023
Merged
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
1 change: 0 additions & 1 deletion apps/chooz/app/detail/[id]/components/CommentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ function CommentContainer({ postId }: Props) {
<Button width="100%" height="48px" variant="outline" onClick={() => fetchNextPage()}>
댓글 더보기
</Button>

<DetailButton width="127px" height="48px" variant="primary" borderRadius="100px">
<Link href={Path.MAIN_PAGE}>
<DetailButtonInner>
Expand Down
2 changes: 1 addition & 1 deletion apps/jurumarble/src/app/my/components/UseInfoContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const AddImageButtonWrapper = styled.div`

const Profile = styled.div`
${({ theme }) => css`
${theme.typography.chip}
${theme.typography.caption_chip}
display: flex;
flex-direction: column;
padding-left: 17px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ const Label = styled.p`
margin-bottom: 4px;
${({ theme }) => theme.typography.body04};
color: ${({ theme }) => theme.colors.black_02};
font-weight: bold;
`;

const Description = styled.p`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const FlexEnd = styled.div`

const WithdrawalButton = styled(Button)`
${({ theme }) => css`
${theme.typography.caption}
${theme.typography.caption_chip}
margin-top: 20px;
width: 58px;
height: 24px;
Expand Down
126 changes: 126 additions & 0 deletions apps/jurumarble/src/app/notification/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"use client";

import { Button } from "components/button";
import VoteHeader from "components/VoteHeader";
import { NotificationType } from "lib/apis/notification";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { DrinkImage } from "public/images";
import { SvgIcPrevious, SvgNotificationCheck } from "src/assets/icons/components";
import styled, { css, useTheme } from "styled-components";
import useNotificationService from "./services/useNotificationService";

const NOTIFICATION_TYPE: Record<NotificationType, string> = {
VOTE: "투표에 10명 이상이 참여했어요.",
COMMENT: "투표에 댓글이 달렸어요.",
ADMIN_NOTIFY: "관리자 알림",
};

function NotificationPage() {
const router = useRouter();
const { notificationList, isLoading, readNotification } = useNotificationService();
const { colors } = useTheme();

return (
<>
<VoteHeader
leftButton={
<PreviousButton onClick={() => router.back()}>
<SvgIcPrevious width={24} height={24} />
</PreviousButton>
}
>
알림
</VoteHeader>
{isLoading ? (
/**
* @TODO 로딩 컴포넌트 추가
*/
<></>
) : !notificationList ? (
<EmptyNotification>
<Image alt="" src={DrinkImage} style={{ borderRadius: "100px" }} />
받은 알림이 없어요.
</EmptyNotification>
) : (
<NotificationList>
{notificationList.map(({ content, createdAt, id, type, isRead }) => (
<NotificationItem key={id} isRead={isRead} onClick={() => readNotification(id)}>
<SvgNotificationCheck
width={32}
height={32}
fill={isRead ? colors.black_04 : colors.main_01}
/>
<ContentBox>
<Content>{content}</Content>
<TypeMessage>{NOTIFICATION_TYPE[type]}</TypeMessage>
<CreatedAt>{createdAt}</CreatedAt>
</ContentBox>
</NotificationItem>
))}
</NotificationList>
)}
</>
);
}

const EmptyNotification = styled.div`
display: flex;
align-items: center;
flex-direction: column;
gap: 22px;
margin-top: 16%;
`;

const PreviousButton = styled(Button)`
${({ theme }) => css`
background-color: ${theme.colors.white};
margin-left: 20px;
`}
`;

const NotificationList = styled.ul`
margin-top: 20px;
`;

const NotificationItem = styled.li<{ isRead: any }>`
${({ theme, isRead }) => css`
background-color: ${isRead && theme.colors.bg_02};
color: ${isRead ? theme.colors.black_03 : theme.colors.black_02};
border-top: 1px solid ${theme.colors.line_01}};
display: flex;
align-items: center;
padding: 16px 20px;
`}
`;

const ContentBox = styled.div`
margin-left: 3%;
display: flex;
flex-direction: column;
flex-grow: 1;
`;

const Content = styled.div`
${({ theme }) => css`
${theme.typography.body04};
`}
`;

const TypeMessage = styled.div`
${({ theme }) => css`
${theme.typography.body03};
`}
`;

const CreatedAt = styled.div`
${({ theme }) => css`
${theme.typography.caption_chip};
color: ${theme.colors.black_04};
display: flex;
width: 100%;
justify-content: flex-end;
`}
`;

export default NotificationPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { getNotificationListAPI, readNotificationAPI } from "lib/apis/notification";
import { queryKeys } from "lib/queryKeys";

type ReadNotificationProps = Exclude<Parameters<typeof readNotificationAPI>[0], undefined>;

const getQueryKey = [queryKeys.NOTIFICATION_LIST];

export default function useNotificationService() {
const { data: notificationList, isLoading } = useQuery(getQueryKey, getNotificationListAPI, {
select: (data) =>
data.map((notification) => {
const createdAtDate = new Date(notification.createdAt);
return {
...notification,
createdAt: `${createdAtDate.getFullYear() - 2000}. ${
createdAtDate.getMonth() + 1
}. ${createdAtDate.getDate()}`,
};
}),
});

const queryClient = useQueryClient();

const { mutate: readNotification } = useMutation(
(notificationId: ReadNotificationProps) => readNotificationAPI(notificationId),
{
async onMutate(notificationId) {
await queryClient.cancelQueries(getQueryKey);
const previousData = queryClient.getQueryData(getQueryKey);
queryClient.setQueryData(getQueryKey, (old: any) => [old, notificationId]);
return { previousData };
},
onError(err, notificationId, context) {
queryClient.setQueryData(getQueryKey, context?.previousData);
},

onSettled() {
queryClient.invalidateQueries({ queryKey: getQueryKey });
},
},
);

return { notificationList, isLoading, readNotification };
}
2 changes: 1 addition & 1 deletion apps/jurumarble/src/app/register/components/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ const Wrapper = styled.div`
`;

const Typography = styled.p`
${({ theme }) => theme.typography.caption};
${({ theme }) => theme.typography.caption_chip};
`;
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ const LeftVote = styled.button<{ selected: ActiveType }>`
&:hover {
border-color: ${({ theme }) => theme.colors.main_01};
color: ${({ theme }) => theme.colors.main_01};
font-weight: 800;
}
`;

Expand All @@ -195,11 +194,10 @@ const RightVote = styled(LeftVote)`

const MbtiType = styled.div`
${({ theme }) => theme.typography.headline02}
font-weight: 800;
`;

const MbtiText = styled.div`
${({ theme }) => theme.typography.chip}
${({ theme }) => theme.typography.caption_chip}
`;

export default RegisterMBTISection;
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ const H2 = styled.h2`
${theme.typography.headline02}
display: flex;
flex-direction: column;
line-height: 26px;
`}
`;

Expand Down
4 changes: 2 additions & 2 deletions apps/jurumarble/src/app/vote/[id]/components/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const TagBox = styled.div`
${({ theme }) => css`
background-color: ${theme.colors.sub_02};
color: ${theme.colors.white};
${theme.typography.caption}
${theme.typography.caption_chip}
`};
line-height: unset;
`;
Expand Down Expand Up @@ -198,7 +198,7 @@ const CommentInfo = styled.div`
gap: 4px;
${({ theme }) => css`
color: ${theme.colors.black_04};
${theme.typography.caption}
${theme.typography.caption_chip}
`}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ const OverlayCount = styled.div`
const OverlayButton = styled.div`
${({ theme }) =>
css`
${theme.typography.chip}
${theme.typography.caption_chip}
background-color: ${theme.colors.main_01};
`}
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const TagBox = styled.div`
${({ theme }) => css`
background-color: ${theme.colors.white};
color: ${theme.colors.black_04};
${theme.typography.caption}
${theme.typography.caption_chip}
`}
`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { SVGProps } from "react";
const SvgNotiIc = (props: SVGProps<SVGSVGElement>) => (
<svg
width="1em"
height="1em"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g id="noti_ic">
<mask
id="mask0_1437_20226"
style={{
maskType: "alpha",
}}
maskUnits="userSpaceOnUse"
x={0}
y={0}
width={32}
height={32}
>
<rect
id="Bounding box"
width={32}
height={32}
fill="current"
vectorEffect="non-scaling-stroke"
/>
</mask>
<g mask="url(#mask0_1437_20226)">
<path
id="expand_circle_down"
d="M16 21.1333L22.6 14.5333L20.51 12.48L16 16.99L11.49 12.48L9.4 14.5333L16 21.1333ZM16 30.6667C13.9711 30.6667 12.0644 30.2817 10.28 29.5117C8.49556 28.7417 6.94334 27.6967 5.62334 26.3767C4.30334 25.0567 3.25834 23.5044 2.48834 21.72C1.71834 19.9356 1.33334 18.0289 1.33334 16C1.33334 13.9711 1.71834 12.0644 2.48834 10.28C3.25834 8.49556 4.30334 6.94334 5.62334 5.62334C6.94334 4.30334 8.49556 3.25834 10.28 2.48834C12.0644 1.71834 13.9711 1.33334 16 1.33334C18.0289 1.33334 19.9356 1.71834 21.72 2.48834C23.5044 3.25834 25.0567 4.30334 26.3767 5.62334C27.6967 6.94334 28.7417 8.49556 29.5117 10.28C30.2817 12.0644 30.6667 13.9711 30.6667 16C30.6667 18.0289 30.2817 19.9356 29.5117 21.72C28.7417 23.5044 27.6967 25.0567 26.3767 26.3767C25.0567 27.6967 23.5044 28.7417 21.72 29.5117C19.9356 30.2817 18.0289 30.6667 16 30.6667ZM16 27.7333C19.2756 27.7333 22.05 26.5967 24.3233 24.3233C26.5967 22.05 27.7333 19.2756 27.7333 16C27.7333 12.7244 26.5967 9.95 24.3233 7.67667C22.05 5.40334 19.2756 4.26667 16 4.26667C12.7244 4.26667 9.95 5.40334 7.67667 7.67667C5.40334 9.95 4.26667 12.7244 4.26667 16C4.26667 19.2756 5.40334 22.05 7.67667 24.3233C9.95 26.5967 12.7244 27.7333 16 27.7333Z"
fill="current"
vectorEffect="non-scaling-stroke"
/>
</g>
</g>
</svg>
);
export default SvgNotiIc;
1 change: 1 addition & 0 deletions apps/jurumarble/src/assets/icons/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ export { default as SvgNotification } from "./IcNotification";
export { default as SvgHeaderSearch } from "./IcHeaderSearch";
export { default as SvgIcThunder } from "./IcThunder";
export { default as SvgWarningIcon } from "./IcWarningIcon";
export { default as SvgNotificationCheck } from "./IcNotificationCheck";
2 changes: 1 addition & 1 deletion apps/jurumarble/src/components/AorBMark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function AorBMark({ AorB, children }: Props) {

const AorBMarkStyled = styled.div<{ AorB: string }>`
${({ theme, AorB }) => css`
${theme.typography.caption}
${theme.typography.caption_chip}
background-color: ${AorB === "A" ? theme.colors.sub_01 : theme.colors.sub_02};
position: absolute;
top: 0;
Expand Down
2 changes: 1 addition & 1 deletion apps/jurumarble/src/components/BottomBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const Inner = styled.div`
display: flex;
margin: 0 auto;
justify-content: space-around;
${({ theme }) => theme.typography.caption};
${({ theme }) => theme.typography.caption_chip};
`;

const BarItem = styled.div<{ isActive: boolean }>`
Expand Down
2 changes: 1 addition & 1 deletion apps/jurumarble/src/components/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const variantStyles = {
const Wrapper = styled.div<variant>`
${({ theme, variant }) => css`
${variant && variantStyles[variant]};
${theme.typography.caption}
${theme.typography.caption_chip}
padding: 10px 8px;
border-radius: 4px;
`}
Expand Down
2 changes: 1 addition & 1 deletion apps/jurumarble/src/components/LevelChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function LevelChip({ level }: Props) {

const Chip = styled.div<{ level: number }>`
${({ theme, level }) => css`
${theme.typography.chip}
${theme.typography.caption_chip}
width: 32px;
height: 20px;
border-radius: 4px;
Expand Down
24 changes: 24 additions & 0 deletions apps/jurumarble/src/lib/apis/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { http } from "./http/http";

export type NotificationType = "VOTE" | "COMMENT" | "ADMIN_NOTIFY";

interface Notification {
id: number;
url: string;
content: string;
type: NotificationType;
isRead: boolean;
createdAt: string;
}

type GetNotificationListResponse = Notification[];

export const getNotificationListAPI = async () => {
const response = await http.get<GetNotificationListResponse>("/api/notifications");
return response.data;
};

export const readNotificationAPI = async (id: number) => {
const response = await http.post(`/api/notifications/${id}/read`);
return response.data;
};
1 change: 1 addition & 0 deletions apps/jurumarble/src/lib/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const queryKeys = {
THE_NUMBER_OF_MY_VOTE: "theNumberOfMyVote" as const,
DRINKS_MAP: "drinksMap" as const,
DRINKS_INFO: "drinksInfo" as const,
NOTIFICATION_LIST: "notificationList" as const,
};

export const reactQueryKeys = {
Expand Down
Loading