diff --git a/frontend/src/components/Modal/index.tsx b/frontend/src/components/Modal/index.tsx index a6a18ab5..2b83730d 100644 --- a/frontend/src/components/Modal/index.tsx +++ b/frontend/src/components/Modal/index.tsx @@ -77,6 +77,7 @@ const Wrapper = styled.div` ${({ position }) => getModalPosition(position)}; top: ${({ top }) => top && top}; left: ${({ left }) => left && left}; + z-index: 2; `; const WrapperDimmed = styled.div<{ $dimmedColor: string }>` @@ -85,6 +86,7 @@ const WrapperDimmed = styled.div<{ $dimmedColor: string }>` position: fixed; top: 0; background-color: ${({ $dimmedColor }) => $dimmedColor}; + z-index: 2; `; const translateModalAnimation = keyframes` diff --git a/frontend/src/components/ModalMyTopicList/index.tsx b/frontend/src/components/ModalMyTopicList/index.tsx new file mode 100644 index 00000000..99112268 --- /dev/null +++ b/frontend/src/components/ModalMyTopicList/index.tsx @@ -0,0 +1,47 @@ +import { Fragment, useEffect, useState } from 'react'; +import { styled } from 'styled-components'; +import { getApi } from '../../apis/getApi'; +import { ModalMyTopicType } from '../../types/Topic'; +import ModalTopicCard from '../ModalTopicCard'; + +const ModalMyTopicList = () => { + const [myTopics, setMyTopics] = useState([]); + + const getMyTopicFromServer = async () => { + const serverMyTopic = await getApi( + 'default', + '/members/my/topics', + ); + setMyTopics(serverMyTopic); + }; + + useEffect(() => { + getMyTopicFromServer(); + }, []); + + if (!myTopics) return <>; + + return ( + + {myTopics.map((topic) => ( + + + + ))} + + ); +}; + +const ModalMyTopicListWrapper = styled.ul` + display: grid; + grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); + grid-row-gap: ${({ theme }) => theme.spacing[5]}; +`; + +export default ModalMyTopicList; diff --git a/frontend/src/components/ModalTopicCard/index.tsx b/frontend/src/components/ModalTopicCard/index.tsx new file mode 100644 index 00000000..1e642b5b --- /dev/null +++ b/frontend/src/components/ModalTopicCard/index.tsx @@ -0,0 +1,115 @@ +import { styled } from 'styled-components'; +import Text from '../common/Text'; +import useNavigator from '../../hooks/useNavigator'; +import Box from '../common/Box'; +import Image from '../common/Image'; +import { SyntheticEvent } from 'react'; +import Space from '../common/Space'; +import Flex from '../common/Flex'; +import SmallTopicPin from '../../assets/smallTopicPin.svg'; +import SmallTopicStar from '../../assets/smallTopicStar.svg'; +import { DEFAULT_TOPIC_IMAGE } from '../../constants'; + +const FAVORITE_COUNT = 10; + +export interface ModalTopicCardProps { + topicId: number; + topicImage: string; + topicTitle: string; + topicUpdatedAt: string; + topicPinCount: number; +} + +const ModalTopicCard = ({ + topicId, + topicImage, + topicTitle, + topicUpdatedAt, + topicPinCount, +}: ModalTopicCardProps) => { + const { routePage } = useNavigator(); + + const goToSelectedTopic = () => { + routePage(`/topics/${topicId}`, [topicId]); + }; + + return ( + + + ) => { + e.currentTarget.src = DEFAULT_TOPIC_IMAGE; + }} + /> + + + + + {topicTitle} + + + + + 토픽 생성자 + + + + + + {topicUpdatedAt.split('T')[0].replaceAll('-', '.')} 업데이트 + + + + + + + + + + {topicPinCount > 999 ? '+999' : topicPinCount}개 + + + + + + + {FAVORITE_COUNT > 999 ? '+999' : FAVORITE_COUNT}명 + + + + + + + ); +}; + +const Wrapper = styled.li` + width: 332px; + height: 140px; + cursor: pointer; + border: 1px solid ${({ theme }) => theme.color.gray}; + border-radius: ${({ theme }) => theme.radius.small}; + + margin: 0 auto; +`; + +const ButtonWrapper = styled.div` + display: flex; + justify-content: space-between; + position: absolute; + width: 72px; + + top: 100px; + left: 60px; +`; + +const TopicImage = styled(Image)` + border-radius: ${({ theme }) => theme.radius.small}; +`; + +export default ModalTopicCard; diff --git a/frontend/src/pages/NewPin.tsx b/frontend/src/pages/NewPin.tsx index 9ba8601c..d3eaea10 100644 --- a/frontend/src/pages/NewPin.tsx +++ b/frontend/src/pages/NewPin.tsx @@ -22,6 +22,7 @@ import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight'; import { ModalContext } from '../context/ModalContext'; import Modal from '../components/Modal'; import { styled } from 'styled-components'; +import ModalMyTopicList from '../components/ModalMyTopicList'; type NewPinFormValueType = Pick< NewPinFormProps, @@ -257,11 +258,21 @@ const NewPin = () => { - 내 토픽 리스트 + + + + 내 토픽 리스트 + + + 핀을 저장할 지도를 선택해주세요. + + + + ); @@ -271,6 +282,10 @@ const ModalContentsWrapper = styled.div` width: 100%; height: 100%; background-color: white; + + text-align: center; + + overflow: scroll; `; export default NewPin; diff --git a/frontend/src/pages/PinDetail.tsx b/frontend/src/pages/PinDetail.tsx index d0f58557..ee434e19 100644 --- a/frontend/src/pages/PinDetail.tsx +++ b/frontend/src/pages/PinDetail.tsx @@ -1,7 +1,7 @@ import Flex from '../components/common/Flex'; import Space from '../components/common/Space'; import Text from '../components/common/Text'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { PinType } from '../types/Pin'; import { getApi } from '../apis/getApi'; import { useSearchParams } from 'react-router-dom'; @@ -11,8 +11,10 @@ import useFormValues from '../hooks/useFormValues'; import { ModifyPinFormProps } from '../types/FormValues'; import useToast from '../hooks/useToast'; import Button from '../components/common/Button'; +import Modal from '../components/Modal'; import { styled } from 'styled-components'; -import { ModalPortal, useModalContext } from '../context/AbsoluteModalContext'; +import { ModalContext } from '../context/ModalContext'; +import ModalMyTopicList from '../components/ModalMyTopicList'; interface PinDetailProps { pinId: number; @@ -28,7 +30,6 @@ const PinDetail = ({ const [searchParams, setSearchParams] = useSearchParams(); const [pin, setPin] = useState(null); const { showToast } = useToast(); - const { isModalOpen, openModal, closeModal } = useModalContext(); const { formValues, errorMessages, @@ -40,6 +41,7 @@ const PinDetail = ({ images: [], description: '', }); + const { openModal } = useContext(ModalContext); useEffect(() => { const getPinData = async () => { @@ -175,7 +177,12 @@ const PinDetail = ({ position="fixed" bottom="24px" > - + { + openModal('saveMyMap'); + }} + > 내 지도에 저장하기 @@ -184,70 +191,25 @@ const PinDetail = ({ - - - {isModalOpen && ( - - <> - - 내 지도 목록 - - - - - 최신순 - - - - 글자순 - - - - 인기순 - - - - - 아이템 - - 아이템 - - 아이템 - - 아이템 - - 아이템 - - - - - - Close Modal - - - - - )} + + + + + 내 토픽 리스트 + + + 핀을 저장할 지도를 선택해주세요. + + + + + ); }; @@ -270,13 +232,14 @@ const ShareButton = styled(Button)` box-shadow: 8px 8px 8px 0px rgba(69, 69, 69, 0.15); `; -const ModalMapItem = styled(Box)` +const ModalContentsWrapper = styled.div` width: 100%; - height: 48px; + height: 100%; + background-color: white; text-align: center; - border: 1px solid black; - border-radius: 8px; + overflow: scroll; `; + export default PinDetail; diff --git a/frontend/src/types/Topic.ts b/frontend/src/types/Topic.ts index ba9bb841..e1832949 100644 --- a/frontend/src/types/Topic.ts +++ b/frontend/src/types/Topic.ts @@ -16,3 +16,13 @@ export interface TopicInfoType { updatedAt: string; pins: PinType[]; } + +export interface ModalMyTopicType { + id: number; + name: string; + image: string; + pinCount: number; + bookmarkCount: number; + isBookmarked: boolean; + updatedAt: string; +} \ No newline at end of file