Skip to content

Commit

Permalink
Merge pull request #91 from wafflestudio/feat/multiple-announcement-m…
Browse files Browse the repository at this point in the history
…odal

Feat/multiple announcement modal
  • Loading branch information
designDefined authored Aug 10, 2023
2 parents 89ce0fb + 4b54de8 commit ab5e685
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 103 deletions.
7 changes: 7 additions & 0 deletions src/apis/announcement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ export const getAllAnnouncements = () =>
getRequest<{ items: TAnnouncement[] }>("/announcements", {}, false).then(
(res) => res.items.reverse(),
);

export const getPinnedAnnouncements = () =>
getRequest<{ items: TAnnouncement[] }>(
"/announcements/pinned",
{},
false,
).then((res) => res.items);
2 changes: 1 addition & 1 deletion src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "styled-components";
import { ModalState } from "./useModal";
import { ModalState } from "./useModals";
import { ReactNode } from "react";
import { zIndex } from "../../lib/zIndex";

Expand Down
37 changes: 0 additions & 37 deletions src/components/Modal/useModal.tsx

This file was deleted.

47 changes: 47 additions & 0 deletions src/components/Modal/useModals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Union } from "../../types/commonTypes";

const modalStates = ["open", "closed", "closing"] as const;
export type ModalState = Union<typeof modalStates>;

export default function useModals(
n: number,
closingTime = 300,
afterClosed = () => void 0,
) {
const [states, setStates] = useState<ModalState[]>(
[...Array(n)].fill("closed"),
);

const openModalById = useCallback(
(id: number) => () => {
setStates((prev) => prev.map((_, idx) => (id === idx ? "open" : _)));
},
[],
);

const timeoutId = useRef<number>();
const closeModalById = useCallback(
(id: number) => () => {
setStates((prev) => prev.map((_, idx) => (id === idx ? "closing" : _)));
timeoutId.current = window.setTimeout(() => {
setStates((prev) => prev.map((_, idx) => (id === idx ? "closed" : _)));
afterClosed();
}, closingTime);
},
[afterClosed, closingTime],
);

// clean up timeout on unmount
useEffect(() => () => window.clearTimeout(timeoutId.current), []);

return useMemo(
() =>
states.map((state, id) => {
const openModal = openModalById(id);
const closeModal = closeModalById(id);
return { state, openModal, closeModal };
}),
[openModalById, closeModalById, states],
);
}
25 changes: 11 additions & 14 deletions src/components/home/NotificationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,28 @@
import styled from "styled-components";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { getAllAnnouncements } from "../../apis/announcement";
import { TAnnouncement } from "../../types/apiTypes";

type NotificationModalProps = {
announcement: TAnnouncement;
index: number;
closeModal: () => void;
setDontShowModalDate: (date: number) => void;
};

export default function NotificationModal({
announcement,
index,
closeModal,
setDontShowModalDate,
}: NotificationModalProps) {
const { data: latestAnnouncement } = useQuery({
queryKey: ["announcement"],
queryFn: () => getAllAnnouncements().then((res) => res[0]),
staleTime: 1,
});
return (
<Article>
<Article $index={index}>
<ContentsWapper>
<ImageContainer>
<img src="/icon/Notification.svg" alt="" />
</ImageContainer>
<Title>{latestAnnouncement?.title}</Title>
<MainText>{latestAnnouncement?.content}</MainText>
{/* TODO : 오류 해결 */}
<Title>{announcement.title}</Title>
<MainText>{announcement.content}</MainText>
<Link to="/announcement/">자세히보기</Link>
</ContentsWapper>
<ButtonWrapper>
Expand All @@ -44,10 +41,10 @@ export default function NotificationModal({
);
}

const Article = styled.article`
const Article = styled.article<{ $index: number }>`
position: fixed;
top: 124px;
left: 164px;
top: ${({ $index }) => `${124 + $index * 50}px`};
left: ${({ $index }) => `${164 + $index * 40}px`};
width: 405px;
border-radius: 15px;
background-color: #fff;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import styled from "styled-components";
import useModal from "../../Modal/useModal";
import useModals from "../../Modal/useModals.tsx";
import Modal from "../../Modal/Modal";
import { BoldText, HorizontalLine, TestCase, Text } from "./common";
import TestCaseModal from "./TestCaseModal";
Expand Down Expand Up @@ -39,7 +39,7 @@ export default function ProblemDescription({
[setCustomTestCases],
);

const modalHandle = useModal();
const [modalHandle] = useModals(1);

return (
// TODO: 데이터 api 연결
Expand Down
108 changes: 59 additions & 49 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,82 @@ import Introduce from "../components/home/Introduce";
import Member from "../components/home/Member";
import Service from "../components/home/Service";
import ToHomePage from "../components/home/ToHomePage";
import useModal from "../components/Modal/useModal";
import useModals from "../components/Modal/useModals";
import Modal from "../components/Modal/Modal";
import NotificationModal from "../components/home/NotificationModal";
import { useEffect } from "react";
import Header from "../components/home/Header/Header";
import { getAllAnnouncements } from "../apis/announcement";
import { getPinnedAnnouncements } from "../apis/announcement";
import { useQuery } from "@tanstack/react-query";

const LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE = "dontShowExpireDate";
const LOCAL_STORAGE_KEY_LATEST_ANNOUNCEMENT_ID = "latestAnnouncementId";
const localStorageKeyOfAnnouncementId = (announcementId: number) =>
`${LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE}_${announcementId}`;

export default function Home() {
const modalHandle = useModal(0);
const { data: latestAnnouncement } = useQuery({
queryKey: ["announcement", "latest"],
queryFn: () => getAllAnnouncements().then((res) => res[0]),
refetchInterval: 1000 * 60,
staleTime: Infinity,
retry: 0,
const { data: pinnedAnnouncements } = useQuery({
queryKey: ["announcement", "pinned"],
queryFn: () => getPinnedAnnouncements().then(),
staleTime: 1,
initialData: [],
});
const modalHandles = useModals(5, 0); // 최대 5개 띄운다
// 공지와 모달핸들러를 하나씩 짝짓는다
const announcementModalHandlePairs = [
...Array(pinnedAnnouncements?.length),
].map((_, idx) => ({
announcement: pinnedAnnouncements[idx],
modalHandle: modalHandles[idx],
}));

useEffect(() => {
const LATEST_ANNOUNCEMENT_ID = Number(
localStorage.getItem(LOCAL_STORAGE_KEY_LATEST_ANNOUNCEMENT_ID),
);
if (
latestAnnouncement &&
latestAnnouncement.id !== LATEST_ANNOUNCEMENT_ID
) {
localStorage.setItem(LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE, "");
localStorage.setItem(
LOCAL_STORAGE_KEY_LATEST_ANNOUNCEMENT_ID,
latestAnnouncement.id.toString(),
);
}

const DONT_SHOW_MODAL_DATE = Number(
localStorage.getItem(LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE),
);
if (!DONT_SHOW_MODAL_DATE) {
modalHandle.openModal();
} else if (new Date().getDate() !== DONT_SHOW_MODAL_DATE) {
modalHandle.openModal();
localStorage.setItem(LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE, "");
}
}, [latestAnnouncement]);
announcementModalHandlePairs.forEach(({ announcement, modalHandle }) => {
// '오늘 그만보기'의 처리를 각 단위마다 실행
const KEY = localStorageKeyOfAnnouncementId(announcement.id);
const DONT_SHOW_MODAL_DATE = Number(localStorage.getItem(KEY));
if (!DONT_SHOW_MODAL_DATE) {
modalHandle.openModal();
} else if (new Date().getDate() !== DONT_SHOW_MODAL_DATE) {
modalHandle.openModal();
localStorage.setItem(KEY, "");
}
});
// api 응답이 성공했을 때 처리하기 위하여 dep array에 pinnedAnnouncements를 추가
}, [pinnedAnnouncements]);

return (
<>
<Header />
<main style={{ minWidth: "920px" }}>
<Modal
handle={modalHandle}
onBackgroundClicked={() => void 0}
modalContainerBackgroundColor="rgba(0, 0, 0, 0.15)"
>
<NotificationModal
closeModal={modalHandle.closeModal}
setDontShowModalDate={(date: number) => {
localStorage.setItem(
LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE,
date.toString(),
);
}}
/>
</Modal>
{announcementModalHandlePairs
.filter(({ announcement }) => {
const KEY = localStorageKeyOfAnnouncementId(announcement.id);
const DONT_SHOW_MODAL_DATE = Number(localStorage.getItem(KEY));
return !DONT_SHOW_MODAL_DATE;
})
.map(({ announcement, modalHandle }, idx) => {
return (
<Modal
key={idx}
handle={modalHandle}
onBackgroundClicked={() => void 0}
// 가장 아래에 위치할 모달을 제외하고는 transparent로 처리
modalContainerBackgroundColor={
idx === 0 ? "rgba(0, 0, 0, 0.15)" : "transparent"
}
>
<NotificationModal
announcement={announcement}
index={idx} // 위치를 조금씩 옮겨주기 위한 prop
closeModal={modalHandle.closeModal}
setDontShowModalDate={(date: number) => {
const KEY = `${LOCAL_STORAGE_KEY_DONT_SHOW_MODAL_DATE}_${announcement.id}`;
localStorage.setItem(KEY, date.toString());
}}
/>
</Modal>
);
})}
<Banner />
<Introduce />
<Member />
Expand Down

0 comments on commit ab5e685

Please sign in to comment.