-
+ {showedImages.map((image, idx) => (
+
+
))}
@@ -306,7 +284,7 @@ const NewPin = () => {
{
-
@@ -343,7 +316,7 @@ const NewPin = () => {
{
>
);
-};
+}
const Wrapper = styled(Flex)`
margin: 0 auto;
diff --git a/frontend/src/pages/NewTopic.stories.ts b/frontend/src/pages/NewTopic.stories.ts
index 2972086b1..4039d65aa 100644
--- a/frontend/src/pages/NewTopic.stories.ts
+++ b/frontend/src/pages/NewTopic.stories.ts
@@ -1,4 +1,5 @@
-import { StoryObj, Meta } from '@storybook/react';
+import { Meta, StoryObj } from '@storybook/react';
+
import NewTopic from './NewTopic';
const meta = {
diff --git a/frontend/src/pages/NewTopic.tsx b/frontend/src/pages/NewTopic.tsx
index 11de14312..7d1a75b20 100644
--- a/frontend/src/pages/NewTopic.tsx
+++ b/frontend/src/pages/NewTopic.tsx
@@ -1,28 +1,29 @@
import { useContext, useEffect, useState } from 'react';
-import Text from '../components/common/Text';
+import { useLocation } from 'react-router-dom';
+import styled from 'styled-components';
+
+import usePost from '../apiHooks/usePost';
+import AuthorityRadioContainer from '../components/AuthorityRadioContainer';
+import Button from '../components/common/Button';
import Flex from '../components/common/Flex';
import Space from '../components/common/Space';
-import Button from '../components/common/Button';
-import useNavigator from '../hooks/useNavigator';
-import { NewTopicFormProps } from '../types/FormValues';
-import useFormValues from '../hooks/useFormValues';
-import { useLocation } from 'react-router-dom';
-import useToast from '../hooks/useToast';
+import Text from '../components/common/Text';
import InputContainer from '../components/InputContainer';
-import { hasErrorMessage, hasNullValue } from '../validations';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
import { LAYOUT_PADDING, SIDEBAR } from '../constants';
-import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
+import { MarkerContext } from '../context/MarkerContext';
import { TagContext } from '../context/TagContext';
-import usePost from '../apiHooks/usePost';
-import AuthorityRadioContainer from '../components/AuthorityRadioContainer';
-import styled from 'styled-components';
import useCompressImage from '../hooks/useCompressImage';
-import { MarkerContext } from '../context/MarkerContext';
+import useFormValues from '../hooks/useFormValues';
+import useNavigator from '../hooks/useNavigator';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
+import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
+import useToast from '../hooks/useToast';
+import { NewTopicFormProps } from '../types/FormValues';
+import { hasErrorMessage, hasNullValue } from '../validations';
type NewTopicFormValuesType = Omit;
-const NewTopic = () => {
+function NewTopic() {
const { routePage } = useNavigator();
const { state: pulledPinIds } = useLocation();
const { showToast } = useToast();
@@ -126,6 +127,7 @@ const NewTopic = () => {
event: React.ChangeEvent,
) => {
const file = event.target.files && event.target.files[0];
+ const currentImage = new Image();
if (!file) {
showToast(
'error',
@@ -135,9 +137,20 @@ const NewTopic = () => {
}
const compressedFile = await compressImage(file);
-
- setFormImage(compressedFile);
- setShowImage(URL.createObjectURL(file));
+ currentImage.src = URL.createObjectURL(compressedFile);
+
+ currentImage.onload = () => {
+ if (currentImage.width < 300) {
+ showToast(
+ 'error',
+ '이미지의 크기가 너무 작습니다. 다른 이미지를 선택해 주세요.',
+ );
+ return;
+ }
+
+ setFormImage(compressedFile);
+ setShowImage(URL.createObjectURL(file));
+ };
};
useEffect(() => {
@@ -170,8 +183,7 @@ const NewTopic = () => {
{showImage && (
<>
- {' '}
- {' '}
+ {' '}
>
)}
@@ -189,7 +201,7 @@ const NewTopic = () => {
{
{
);
-};
+}
const Wrapper = styled(Flex)`
margin: 0 auto;
diff --git a/frontend/src/pages/NotFound.tsx b/frontend/src/pages/NotFound.tsx
index 3ce270a4e..72096ed60 100644
--- a/frontend/src/pages/NotFound.tsx
+++ b/frontend/src/pages/NotFound.tsx
@@ -1,14 +1,15 @@
import { styled } from 'styled-components';
+
import NotFoundIcon from '../assets/NotFoundIcon.svg';
-import useNavigator from '../hooks/useNavigator';
import Button from '../components/common/Button';
import Flex from '../components/common/Flex';
import Space from '../components/common/Space';
import Text from '../components/common/Text';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
import { FULLSCREEN } from '../constants';
+import useNavigator from '../hooks/useNavigator';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-const NotFound = () => {
+function NotFound() {
const { routePage } = useNavigator();
useSetLayoutWidth(FULLSCREEN);
@@ -39,7 +40,7 @@ const NotFound = () => {
);
-};
+}
const NotFoundContainer = styled(Flex)`
flex-direction: row;
@@ -49,7 +50,7 @@ const NotFoundContainer = styled(Flex)`
`;
const NotFoundButton = styled(Button)`
- font-weight: ${({ theme }) => theme.fontWeight['bold']};
+ font-weight: ${({ theme }) => theme.fontWeight.bold};
&:hover {
color: ${({ theme }) => theme.color.white};
diff --git a/frontend/src/pages/PinDetail.stories.ts b/frontend/src/pages/PinDetail.stories.ts
index 29ce16b02..4a02df74f 100644
--- a/frontend/src/pages/PinDetail.stories.ts
+++ b/frontend/src/pages/PinDetail.stories.ts
@@ -1,4 +1,5 @@
-import { StoryObj, Meta } from '@storybook/react';
+import { Meta, StoryObj } from '@storybook/react';
+
import PinDetail from './PinDetail';
const meta = {
diff --git a/frontend/src/pages/PinDetail.tsx b/frontend/src/pages/PinDetail.tsx
index 1706b7def..0e909fa3d 100644
--- a/frontend/src/pages/PinDetail.tsx
+++ b/frontend/src/pages/PinDetail.tsx
@@ -1,23 +1,29 @@
-import Flex from '../components/common/Flex';
-import Space from '../components/common/Space';
-import Text from '../components/common/Text';
import { useContext, useEffect, useState } from 'react';
-import { PinProps } from '../types/Pin';
-import { getApi } from '../apis/getApi';
import { useSearchParams } from 'react-router-dom';
+import { styled } from 'styled-components';
+
+import { getApi } from '../apis/getApi';
+import { postApi } from '../apis/postApi';
+import UpdateBtnSVG from '../assets/updateBtn.svg';
import Box from '../components/common/Box';
-import UpdatedPinDetail from './UpdatedPinDetail';
-import useFormValues from '../hooks/useFormValues';
-import { ModifyPinFormProps } from '../types/FormValues';
-import useToast from '../hooks/useToast';
import Button from '../components/common/Button';
+import Flex from '../components/common/Flex';
+import SingleComment, {
+ ProfileImage,
+} from '../components/common/Input/SingleComment';
+import Space from '../components/common/Space';
+import Text from '../components/common/Text';
import Modal from '../components/Modal';
-import { styled } from 'styled-components';
-import { ModalContext } from '../context/ModalContext';
import AddToMyTopicList from '../components/ModalMyTopicList/addToMyTopicList';
-import { postApi } from '../apis/postApi';
import PinImageContainer from '../components/PinImageContainer';
+import { ModalContext } from '../context/ModalContext';
import useCompressImage from '../hooks/useCompressImage';
+import useFormValues from '../hooks/useFormValues';
+import useToast from '../hooks/useToast';
+import theme from '../themes';
+import { ModifyPinFormProps } from '../types/FormValues';
+import { PinProps } from '../types/Pin';
+import UpdatedPinDetail from './UpdatedPinDetail';
interface PinDetailProps {
width: '372px' | '100vw';
@@ -26,16 +32,20 @@ interface PinDetailProps {
setIsEditPinDetail: React.Dispatch>;
}
-const userToken = localStorage.getItem('userToken');
+const userToken = localStorage?.getItem('userToken');
+const localStorageUser = localStorage?.getItem('user');
+const user = JSON.parse(localStorageUser || '{}');
-const PinDetail = ({
+function PinDetail({
width,
pinId,
isEditPinDetail,
setIsEditPinDetail,
-}: PinDetailProps) => {
+}: PinDetailProps) {
const [searchParams, setSearchParams] = useSearchParams();
const [pin, setPin] = useState(null);
+ const [commentList, setCommentList] = useState([]); // 댓글 리스트
+ const [newComment, setNewComment] = useState('');
const { showToast } = useToast();
const {
formValues,
@@ -70,6 +80,7 @@ const PinDetail = ({
useEffect(() => {
getPinData();
+ setCurrentPageCommentList(pinId);
}, [pinId, searchParams]);
const onClickEditPin = () => {
@@ -117,6 +128,40 @@ const PinDetail = ({
getPinData();
};
+ // 댓글 구현 부분
+ const setCurrentPageCommentList = async (pinId: number) => {
+ const data = await getApi(`/pins/${pinId}/comments`);
+ setCommentList(data);
+ return data;
+ };
+
+ useEffect(() => {
+ setCurrentPageCommentList(pinId);
+ }, []);
+
+ const onClickCommentBtn = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+
+ try {
+ // 댓글 추가
+ // comment 값이랑 추가 정보 body에 담아서 보내기
+ await postApi(
+ `/pins/comments`,
+ {
+ pinId,
+ content: newComment,
+ parentPinCommentId: null,
+ },
+ 'application/json',
+ );
+
+ setCurrentPageCommentList(pinId);
+ setNewComment('');
+ showToast('info', '댓글이 추가되었습니다.');
+ } catch {
+ showToast('error', '댓글을 다시 작성해주세요');
+ }
+ };
if (!pin) return <>>;
if (isEditPinDetail)
@@ -142,9 +187,7 @@ const PinDetail = ({
{pin.name}
-
-
{pin.creator}
@@ -152,14 +195,7 @@ const PinDetail = ({
{pin.canUpdate ? (
-
- 수정하기
-
+
) : (
@@ -169,21 +205,18 @@ const PinDetail = ({
-
-
- 파일업로드
+ {userToken && (
+ 파일업로드
+ )}
-
-
-
+
-
어디에 있나요?
@@ -203,8 +236,7 @@ const PinDetail = ({
{pin.description}
-
-
+
@@ -218,6 +250,49 @@ const PinDetail = ({
+ {/* Comment Section */}
+
+
+ 어떻게 생각하나요?{' '}
+
+
+ {userToken && (
+
+
+
+ ) =>
+ setNewComment(e.target.value)
+ }
+ placeholder="댓글 추가"
+ />
+
+ 등록
+
+
+
+ )}
+ {commentList?.length > 0 &&
+ commentList.map(
+ (comment) =>
+ !comment.parentPinCommentId ? (
+
+ ) : null, // <-- comment.parentPinCommentId가 존재하는 경우 null 반환
+ )}
+ {/* comment section END */}
+
+
);
-};
+}
const Wrapper = styled.section<{
$layoutWidth: '372px' | '100vw';
@@ -307,7 +382,7 @@ const ButtonsWrapper = styled.div`
align-items: center;
width: 332px;
height: 48px;
- margin: 0 auto;
+ margin: 24px auto 0;
`;
const ImageInputLabel = styled.label`
@@ -329,4 +404,28 @@ const ImageInputButton = styled.input`
display: none;
`;
+export const CustomInput = styled.input`
+ width: 100%;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+ border-bottom: 2px solid ${theme.color.lightGray};
+ font-size: 16px;
+
+ &:focus {
+ outline: none;
+ border-bottom: 2px solid ${theme.color.black};
+ }
+`;
+
+export const ConfirmCommentButton = styled(Button)`
+ font-size: ${({ theme }) => theme.fontSize.extraSmall};
+ font-weight: ${({ theme }) => theme.fontWeight.bold};
+
+ box-shadow: 8px 8px 8px 0px rgba(69, 69, 69, 0.15);
+
+ margin-top: 12px;
+ float: right;
+ font-size: 12px;
+`;
export default PinDetail;
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index abafac264..33345e3d7 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -1,20 +1,21 @@
+import { lazy, Suspense } from 'react';
import { styled } from 'styled-components';
+
import Box from '../components/common/Box';
import Flex from '../components/common/Flex';
import Space from '../components/common/Space';
+import MediaSpace from '../components/common/Space/MediaSpace';
+import MediaText from '../components/common/Text/MediaText';
import MyInfo from '../components/MyInfo';
-import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-import { FULLSCREEN } from '../constants';
import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
-import { Suspense, lazy } from 'react';
-import Text from '../components/common/Text';
+import { FULLSCREEN } from '../constants';
import useNavigator from '../hooks/useNavigator';
-import { setFullScreenResponsive } from '../constants/responsive';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
+import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
const TopicCardList = lazy(() => import('../components/TopicCardList'));
-const Profile = () => {
+function Profile() {
const { routePage } = useNavigator();
useSetLayoutWidth(FULLSCREEN);
useSetNavbarHighlight('profile');
@@ -33,27 +34,27 @@ const Profile = () => {
-
나의 지도
-
+
-
내가 만든 지도를 확인해보세요.
-
+
-
+
}>
{
routePage={goToNewTopic}
/>
+
+
);
-};
+}
const Wrapper = styled(Box)`
- width: 1036px;
+ width: 1140px;
margin: 0 auto;
+ position: relative;
- ${setFullScreenResponsive()}
+ @media (max-width: 1180px) {
+ width: 100%;
+ }
`;
const MyInfoWrapper = styled(Flex)`
diff --git a/frontend/src/pages/RootPage.tsx b/frontend/src/pages/RootPage.tsx
index a0934c53d..8bcaec993 100644
--- a/frontend/src/pages/RootPage.tsx
+++ b/frontend/src/pages/RootPage.tsx
@@ -1,10 +1,11 @@
import { Outlet } from 'react-router-dom';
+
import Layout from '../components/Layout';
import LayoutWidthProvider from '../context/LayoutWidthContext';
import NavbarHighlightsProvider from '../context/NavbarHighlightsContext';
import RouteChangeTracker from '../utils/RouteChangeTracker';
-const RootPage = () => {
+function RootPage() {
RouteChangeTracker();
return (
<>
@@ -17,6 +18,6 @@ const RootPage = () => {
>
);
-};
+}
export default RootPage;
diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx
new file mode 100644
index 000000000..ff5a00fb7
--- /dev/null
+++ b/frontend/src/pages/Search.tsx
@@ -0,0 +1,146 @@
+import { Fragment, useEffect, useState } from 'react';
+import { useLocation } from 'react-router-dom';
+import styled from 'styled-components';
+
+import useGet from '../apiHooks/useGet';
+import Box from '../components/common/Box';
+import Flex from '../components/common/Flex';
+import Grid from '../components/common/Grid';
+import Space from '../components/common/Space';
+import MediaText from '../components/common/Text/MediaText';
+import SearchBar from '../components/SearchBar/SearchBar';
+import TopicCard from '../components/TopicCard';
+import { TopicCardProps } from '../types/Topic';
+
+function Search() {
+ const { fetchGet } = useGet();
+
+ const [originalTopics, setOriginalTopics] = useState(
+ null,
+ );
+ const [displayedTopics, setDisplayedTopics] = useState<
+ TopicCardProps[] | null
+ >(null);
+ const searchQuery = decodeURIComponent(useLocation().search.substring(1));
+
+ const getTopicsFromServer = async () => {
+ fetchGet(
+ '/topics',
+ '지도를 가져오는데 실패했습니다.',
+ (response) => {
+ setOriginalTopics(response);
+ const searchResult = response.filter((topic) =>
+ topic.name.includes(searchQuery),
+ );
+ setDisplayedTopics(searchResult);
+ },
+ );
+ };
+
+ useEffect(() => {
+ getTopicsFromServer();
+ }, []);
+
+ useEffect(() => {
+ if (originalTopics) {
+ const searchResult = originalTopics.filter((topic) =>
+ topic.name.includes(searchQuery),
+ );
+ setDisplayedTopics(searchResult);
+ }
+ }, [searchQuery]);
+
+ return (
+
+
+
+
+
+
+
+ 찾았을 지도?
+
+
+
+ {`${searchQuery} 검색 결과입니다.`}
+
+
+
+
+
+ {displayedTopics?.length === 0 ? (
+ // 검색 결과가 없을 때의 UI
+
+
+
+
+ {`'${searchQuery}'에 대한 검색 결과가 없습니다.`}
+
+
+
+
+
+ ) : (
+
+ {displayedTopics?.map((topic) => (
+
+
+
+ ))}
+
+ )}
+
+
+ );
+}
+
+export default Search;
+
+const Wrapper = styled.article`
+ width: 1140px;
+ margin: 0 auto;
+ position: relative;
+
+ @media (max-width: 1180px) {
+ width: 100%;
+ }
+`;
+
+const EmptyWrapper = styled.section`
+ height: 240px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
diff --git a/frontend/src/pages/SeeAllLatestTopics.tsx b/frontend/src/pages/SeeAllLatestTopics.tsx
index e949f1a4c..93cff787f 100644
--- a/frontend/src/pages/SeeAllLatestTopics.tsx
+++ b/frontend/src/pages/SeeAllLatestTopics.tsx
@@ -1,18 +1,19 @@
+import { lazy, Suspense } from 'react';
import { styled } from 'styled-components';
+
+import Box from '../components/common/Box';
import Space from '../components/common/Space';
-import Text from '../components/common/Text';
+import MediaSpace from '../components/common/Space/MediaSpace';
+import MediaText from '../components/common/Text/MediaText';
+import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
import { FULLSCREEN } from '../constants';
+import useNavigator from '../hooks/useNavigator';
import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
-import Box from '../components/common/Box';
-import { Suspense, lazy } from 'react';
-import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
-import useNavigator from '../hooks/useNavigator';
-import { setFullScreenResponsive } from '../constants/responsive';
const TopicCardList = lazy(() => import('../components/TopicCardList'));
-const SeeAllLatestTopics = () => {
+function SeeAllLatestTopics() {
const { routePage } = useNavigator();
useSetLayoutWidth(FULLSCREEN);
useSetNavbarHighlight('home');
@@ -24,11 +25,11 @@ const SeeAllLatestTopics = () => {
return (
-
+
새로울 지도?
-
+
-
+
}>
{
routePage={goToHome}
/>
+
+
);
-};
+}
const Wrapper = styled(Box)`
- width: 1036px;
+ width: 1140px;
margin: 0 auto;
+ position: relative;
- ${setFullScreenResponsive()}
+ @media (max-width: 1180px) {
+ width: 100%;
+ }
`;
export default SeeAllLatestTopics;
diff --git a/frontend/src/pages/SeeAllNearTopics.tsx b/frontend/src/pages/SeeAllNearTopics.tsx
index 15cd30357..022d2a414 100644
--- a/frontend/src/pages/SeeAllNearTopics.tsx
+++ b/frontend/src/pages/SeeAllNearTopics.tsx
@@ -1,18 +1,19 @@
+import { lazy, Suspense } from 'react';
import { styled } from 'styled-components';
-import { FULLSCREEN } from '../constants';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
+
import Box from '../components/common/Box';
import Space from '../components/common/Space';
-import Text from '../components/common/Text';
+import MediaSpace from '../components/common/Space/MediaSpace';
+import MediaText from '../components/common/Text/MediaText';
import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
-import { Suspense, lazy } from 'react';
+import { FULLSCREEN } from '../constants';
import useNavigator from '../hooks/useNavigator';
-import { setFullScreenResponsive } from '../constants/responsive';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
+import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
const TopicCardList = lazy(() => import('../components/TopicCardList'));
-const SeeAllNearTopics = () => {
+function SeeAllNearTopics() {
const { routePage } = useNavigator();
useSetLayoutWidth(FULLSCREEN);
useSetNavbarHighlight('home');
@@ -24,11 +25,11 @@ const SeeAllNearTopics = () => {
return (
-
+
내 주변일 지도?
-
+
-
+
}>
{
routePage={goToHome}
/>
+
+
);
-};
+}
const Wrapper = styled(Box)`
- width: 1036px;
+ width: 1140px;
margin: 0 auto;
+ position: relative;
- ${setFullScreenResponsive()}
+ @media (max-width: 1180px) {
+ width: 100%;
+ }
`;
export default SeeAllNearTopics;
diff --git a/frontend/src/pages/SeeAllPopularTopics.tsx b/frontend/src/pages/SeeAllPopularTopics.tsx
index d40d25c0e..275a402b5 100644
--- a/frontend/src/pages/SeeAllPopularTopics.tsx
+++ b/frontend/src/pages/SeeAllPopularTopics.tsx
@@ -1,18 +1,19 @@
-import Text from '../components/common/Text';
-import Space from '../components/common/Space';
+import { lazy, Suspense } from 'react';
import { styled } from 'styled-components';
+
import Box from '../components/common/Box';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-import { FULLSCREEN } from '../constants';
-import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
-import { Suspense, lazy } from 'react';
+import Space from '../components/common/Space';
+import MediaSpace from '../components/common/Space/MediaSpace';
+import MediaText from '../components/common/Text/MediaText';
import TopicCardContainerSkeleton from '../components/Skeletons/TopicListSkeleton';
+import { FULLSCREEN } from '../constants';
import useNavigator from '../hooks/useNavigator';
-import { setFullScreenResponsive } from '../constants/responsive';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
+import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
const TopicCardList = lazy(() => import('../components/TopicCardList'));
-const SeeAllTopics = () => {
+function SeeAllTopics() {
const { routePage } = useNavigator();
useSetLayoutWidth(FULLSCREEN);
useSetNavbarHighlight('home');
@@ -24,11 +25,11 @@ const SeeAllTopics = () => {
return (
-
+
인기 급상승할 지도?
-
+
-
+
}>
{
routePage={goToHome}
/>
+
+
);
-};
+}
const Wrapper = styled(Box)`
- width: 1036px;
+ width: 1140px;
margin: 0 auto;
+ position: relative;
- ${setFullScreenResponsive()}
+ @media (max-width: 1180px) {
+ width: 100%;
+ }
`;
export default SeeAllTopics;
diff --git a/frontend/src/pages/SeeTogether.tsx b/frontend/src/pages/SeeTogether.tsx
new file mode 100644
index 000000000..c13cef557
--- /dev/null
+++ b/frontend/src/pages/SeeTogether.tsx
@@ -0,0 +1,263 @@
+import { Fragment, Suspense, useContext, useEffect, useState } from 'react';
+import { useLocation, useParams, useSearchParams } from 'react-router-dom';
+import { styled } from 'styled-components';
+
+import { getApi } from '../apis/getApi';
+import SeeTogetherNotFilledSVG from '../assets/seeTogetherBtn_notFilled.svg';
+import Button from '../components/common/Button';
+import Flex from '../components/common/Flex';
+import Space from '../components/common/Space';
+import Text from '../components/common/Text';
+import PinsOfTopic from '../components/PinsOfTopic';
+import PullPin from '../components/PullPin';
+import PinsOfTopicSkeleton from '../components/Skeletons/PinsOfTopicSkeleton';
+import { LAYOUT_PADDING, SIDEBAR } from '../constants';
+import { CoordinatesContext } from '../context/CoordinatesContext';
+import { MarkerContext } from '../context/MarkerContext';
+import { SeeTogetherContext } from '../context/SeeTogetherContext';
+import useNavigator from '../hooks/useNavigator';
+import useResizeMap from '../hooks/useResizeMap';
+import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
+import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
+import useTags from '../hooks/useTags';
+import { PinProps } from '../types/Pin';
+import { TopicDetailProps } from '../types/Topic';
+import PinDetail from './PinDetail';
+
+function SeeTogether() {
+ const accessToken = localStorage.getItem('userToken');
+
+ const { topicId } = useParams();
+ const { routePage } = useNavigator();
+ const [searchParams, _] = useSearchParams();
+ const location = useLocation();
+
+ const [isOpen, setIsOpen] = useState(true);
+ const [isEditPinDetail, setIsEditPinDetail] = useState(false);
+ const [selectedPinId, setSelectedPinId] = useState(null);
+ const [topicDetails, setTopicDetails] = useState(
+ null,
+ );
+
+ const { tags, setTags, onClickInitTags, onClickCreateTopicWithTags } =
+ useTags();
+ const { setCoordinates } = useContext(CoordinatesContext);
+ const { width } = useSetLayoutWidth(SIDEBAR);
+ const { seeTogetherTopics } = useContext(SeeTogetherContext);
+ const { markers, removeMarkers, removeInfowindows } =
+ useContext(MarkerContext);
+ useSetNavbarHighlight('seeTogether');
+ useResizeMap();
+
+ const goToHome = () => {
+ routePage('/');
+ };
+
+ const getAndSetDataFromServer = async () => {
+ if (topicId === '-1' || !topicId) return;
+
+ const requestTopicIds = accessToken
+ ? topicId
+ : seeTogetherTopics?.join(',');
+
+ const topics = await getApi(
+ `/topics/ids?ids=${requestTopicIds}`,
+ );
+
+ setTopicDetails([...topics]);
+ setCoordinatesTopicDetailWithHashMap(topics);
+ };
+
+ const setCoordinatesTopicDetailWithHashMap = (topics: TopicDetailProps[]) => {
+ if (topicId === '-1' || !topicId) return;
+
+ const newCoordinates: any = [];
+
+ topics.forEach((topic: TopicDetailProps) => {
+ topic.pins.forEach((pin: PinProps) => {
+ newCoordinates.push({
+ id: pin.id,
+ topicId: topic.id,
+ pinName: pin.name,
+ latitude: pin.latitude,
+ longitude: pin.longitude,
+ });
+ });
+ });
+
+ setCoordinates(newCoordinates);
+ };
+
+ const togglePinDetail = () => {
+ setIsOpen(!isOpen);
+ };
+
+ useEffect(() => {
+ const queryParams = new URLSearchParams(location.search);
+
+ if (queryParams.has('pinDetail')) {
+ setSelectedPinId(Number(queryParams.get('pinDetail')));
+ setIsOpen(true);
+ return;
+ }
+
+ setSelectedPinId(null);
+ }, [searchParams]);
+
+ useEffect(() => {
+ getAndSetDataFromServer();
+ }, [topicId]);
+
+ useEffect(() => {
+ setTags([]);
+
+ if (markers && markers.length > 0) {
+ removeMarkers();
+ removeInfowindows();
+ }
+ }, []);
+
+ if (!seeTogetherTopics || !topicId) return <>>;
+
+ if (seeTogetherTopics.length === 0 || topicId === '-1') {
+ return (
+
+
+
+
+
+ 버튼을 눌러 지도를 추가해보세요.
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {tags.length > 0 && (
+
+ )}
+ }>
+ {topicDetails?.map((topicDetail, idx) => (
+
+ topicDetail.id)
+ .join(',')}
+ topicId={String(topicDetail.id)}
+ topicDetail={topicDetail}
+ setSelectedPinId={setSelectedPinId}
+ setIsEditPinDetail={setIsEditPinDetail}
+ setTopicsFromServer={getAndSetDataFromServer}
+ />
+ {idx !== topicDetails.length - 1 ? : null}
+
+ ))}
+
+
+
+
+ {selectedPinId && (
+ <>
+
+ ◀
+
+
+
+
+ >
+ )}
+
+ );
+}
+
+const WrapperWhenEmpty = styled.section<{ width: '372px' | '100vw' }>`
+ width: ${({ width }) => `calc(${width} - 40px)`};
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+
+ margin: 0 auto;
+`;
+
+const Wrapper = styled.section<{
+ width: 'calc(100vw - 40px)' | 'calc(372px - 40px)';
+ $selectedPinId: number | null;
+}>`
+ display: flex;
+ flex-direction: column;
+ width: ${({ width }) => width};
+ margin: 0 auto;
+
+ @media (max-width: 1076px) {
+ width: ${({ $selectedPinId }) => ($selectedPinId ? '49vw' : '50vw')};
+ margin: ${({ $selectedPinId }) => $selectedPinId && '0'};
+ }
+
+ @media (max-width: 744px) {
+ width: 100%;
+ }
+`;
+
+const PinDetailWrapper = styled.div`
+ &.collapsedPinDetail {
+ z-index: -1;
+ }
+`;
+
+const ToggleButton = styled.button<{
+ $isCollapsed: boolean;
+}>`
+ position: absolute;
+ top: 50%;
+ left: 744px;
+ transform: translateY(-50%);
+ z-index: 1;
+ height: 80px;
+ background-color: #fff;
+ padding: 12px;
+ border-radius: 4px;
+ box-shadow: 0px 1px 5px rgba(0, 0, 0, 0.2);
+ cursor: pointer;
+
+ ${(props) =>
+ props.$isCollapsed &&
+ `
+ transform: rotate(180deg);
+ top: 45%;
+ left: 372px;
+ z-index: 1;
+ `}
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ @media (max-width: 1076px) {
+ display: none;
+ }
+`;
+
+export default SeeTogether;
diff --git a/frontend/src/pages/SeeTogetherTopics.tsx b/frontend/src/pages/SeeTogetherTopics.tsx
deleted file mode 100644
index a4c5a0af6..000000000
--- a/frontend/src/pages/SeeTogetherTopics.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import { useContext } from 'react';
-import { SIDEBAR } from '../constants';
-import useNavigator from '../hooks/useNavigator';
-import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-import { SeeTogetherContext } from '../context/SeeTogetherContext';
-import Text from '../components/common/Text';
-import SeeTogetherNotFilledSVG from '../assets/seeTogetherBtn_notFilled.svg';
-import { styled } from 'styled-components';
-import Flex from '../components/common/Flex';
-import Space from '../components/common/Space';
-import TopicCard from '../components/TopicCard';
-import Button from '../components/common/Button';
-import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
-import { deleteApi } from '../apis/deleteApi';
-import useToast from '../hooks/useToast';
-import { getApi } from '../apis/getApi';
-import { TopicCardProps } from '../types/Topic';
-
-const SeeTogetherTopics = () => {
- const { routePage } = useNavigator();
- const { width } = useSetLayoutWidth(SIDEBAR);
- const { seeTogetherTopics, setSeeTogetherTopics } =
- useContext(SeeTogetherContext);
- const { showToast } = useToast();
- useSetNavbarHighlight('seeTogether');
-
- const goToHome = () => {
- routePage('/');
- };
-
- const setTopicsFromServer = async () => {
- try {
- const topics = await getApi('/members/my/atlas');
-
- setSeeTogetherTopics(topics);
- } catch {
- showToast('error', '로그인 후 이용해주세요.');
- }
- };
-
- const goToSelectedTopic = () => {
- if (!seeTogetherTopics) return;
-
- const seeTogetherTopicIds = seeTogetherTopics
- .map((topic) => topic.id)
- .join(',');
-
- routePage(`/topics/${seeTogetherTopicIds}`, seeTogetherTopicIds);
- };
-
- const onClickDeleteSeeTogetherTopics = () => {
- if (!seeTogetherTopics) return;
-
- const deleteTopics = seeTogetherTopics;
-
- try {
- deleteTopics.forEach(async (topic) => {
- await deleteApi(`/atlas/topics?id=${topic.id}`);
- });
-
- showToast('info', '모아보기를 비웠습니다.');
-
- setSeeTogetherTopics([]);
- } catch (e) {
- showToast('info', '모아보기를 비우는데 실패했습니다.');
- }
- };
-
- if (!seeTogetherTopics) return <>>;
-
- if (seeTogetherTopics.length === 0) {
- return (
-
-
-
-
-
- 버튼을 눌러 지도를 추가해보세요.
-
-
-
-
-
-
- );
- }
-
- return (
-
-
- {seeTogetherTopics.map((topic, idx) => (
-
-
- {idx !== seeTogetherTopics.length - 1 ? : <>>}
-
- ))}
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const Wrapper = styled.section<{ width: '372px' | '100vw' }>`
- width: ${({ width }) => `calc(${width} - 40px)`};
- height: 100%;
- display: flex;
- flex-direction: column;
-
- margin: 0 auto;
-`;
-
-const WrapperWhenEmpty = styled.section<{ width: '372px' | '100vw' }>`
- width: ${({ width }) => `calc(${width} - 40px)`};
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-
- margin: 0 auto;
-`;
-
-const ButtonsWrapper = styled.div`
- display: flex;
- justify-content: end;
- align-items: center;
-`;
-
-export default SeeTogetherTopics;
diff --git a/frontend/src/pages/SelectedTopic.tsx b/frontend/src/pages/SelectedTopic.tsx
index 57c5067eb..45e63dd8c 100644
--- a/frontend/src/pages/SelectedTopic.tsx
+++ b/frontend/src/pages/SelectedTopic.tsx
@@ -1,98 +1,77 @@
-import {
- Fragment,
- lazy,
- Suspense,
- useContext,
- useEffect,
- useState,
-} from 'react';
-import { styled } from 'styled-components';
-import Space from '../components/common/Space';
-import { TopicDetailProps } from '../types/Topic';
+import { lazy, Suspense, useContext, useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
-import PinDetail from './PinDetail';
+import { styled } from 'styled-components';
+
import { getApi } from '../apis/getApi';
+import Space from '../components/common/Space';
import PullPin from '../components/PullPin';
+import PinsOfTopicSkeleton from '../components/Skeletons/PinsOfTopicSkeleton';
+import { LAYOUT_PADDING, SIDEBAR } from '../constants';
import { CoordinatesContext } from '../context/CoordinatesContext';
-import useNavigator from '../hooks/useNavigator';
+import useResizeMap from '../hooks/useResizeMap';
import useSetLayoutWidth from '../hooks/useSetLayoutWidth';
-import { LAYOUT_PADDING, SIDEBAR } from '../constants';
import useSetNavbarHighlight from '../hooks/useSetNavbarHighlight';
-import PinsOfTopicSkeleton from '../components/Skeletons/PinsOfTopicSkeleton';
-import { TagContext } from '../context/TagContext';
+import useTags from '../hooks/useTags';
import { PinProps } from '../types/Pin';
+import { TopicDetailProps } from '../types/Topic';
+import PinDetail from './PinDetail';
const PinsOfTopic = lazy(() => import('../components/PinsOfTopic'));
-const SelectedTopic = () => {
+function SelectedTopic() {
const { topicId } = useParams();
const [searchParams, _] = useSearchParams();
- const [topicDetails, setTopicDetails] = useState(
- null,
- );
+ const [topicDetail, setTopicDetail] = useState(null);
const [selectedPinId, setSelectedPinId] = useState(null);
const [isOpen, setIsOpen] = useState(true);
const [isEditPinDetail, setIsEditPinDetail] = useState(false);
- const { routePage } = useNavigator();
const { setCoordinates } = useContext(CoordinatesContext);
- const { tags, setTags } = useContext(TagContext);
const { width } = useSetLayoutWidth(SIDEBAR);
- const { navbarHighlights: __ } = useSetNavbarHighlight('home');
+ const { tags, setTags, onClickInitTags, onClickCreateTopicWithTags } =
+ useTags();
+ useSetNavbarHighlight('none');
+ useResizeMap();
const getAndSetDataFromServer = async () => {
- const data = await getApi(`/topics/ids?ids=${topicId}`);
-
- const topicHashmap = new Map([]);
+ const topicInArray = await getApi(
+ `/topics/ids?ids=${topicId}`,
+ );
+ const topic = topicInArray[0];
- setTopicDetails(data);
+ setTopicDetail(topic);
+ setCoordinatesTopicDetail(topic);
+ };
- // 각 topic의 pin들의 좌표를 가져옴
+ const setCoordinatesTopicDetail = (topic: TopicDetailProps) => {
const newCoordinates: any = [];
- data.forEach((topic: TopicDetailProps) => {
- topic.pins.forEach((pin: PinProps) => {
- newCoordinates.push({
- id: pin.id,
- topicId: topic.id,
- pinName: pin.name,
- latitude: pin.latitude,
- longitude: pin.longitude,
- });
+ topic.pins.forEach((pin: PinProps) => {
+ newCoordinates.push({
+ id: pin.id,
+ topicId,
+ pinName: pin.name,
+ latitude: pin.latitude,
+ longitude: pin.longitude,
});
});
setCoordinates(newCoordinates);
-
- data.forEach((topicDetailFromData: TopicDetailProps) =>
- topicHashmap.set(`${topicDetailFromData.id}`, topicDetailFromData),
- );
-
- const topicDetailFromData = topicId
- ?.split(',')
- .map((number) => topicHashmap.get(number)) as TopicDetailProps[];
-
- if (!topicDetailFromData) return;
-
- setTopicDetails([...topicDetailFromData]);
- };
-
- const onClickConfirm = () => {
- routePage('/new-topic', tags.map((tag) => tag.id).join(','));
};
- const onTagCancel = () => {
- setTags([]);
+ const togglePinDetail = () => {
+ setIsOpen(!isOpen);
};
useEffect(() => {
const queryParams = new URLSearchParams(location.search);
+
if (queryParams.has('pinDetail')) {
setSelectedPinId(Number(queryParams.get('pinDetail')));
- } else {
- setSelectedPinId(null);
+ setIsOpen(true);
+ return;
}
- setIsOpen(true);
+ setSelectedPinId(null);
}, [searchParams]);
useEffect(() => {
@@ -100,12 +79,7 @@ const SelectedTopic = () => {
setTags([]);
}, []);
- const togglePinDetail = () => {
- setIsOpen(!isOpen);
- };
-
- if (!topicDetails) return <>>;
- if (!topicId) return <>>;
+ if (!topicId || !topicDetail) return <>>;
return (
{
)}
}>
- {topicDetails.map((topicDetail, idx) => (
-
-
- {idx !== topicDetails.length - 1 ? : <>>}
-
- ))}
+
+
+
{selectedPinId && (
<>
@@ -153,7 +124,7 @@ const SelectedTopic = () => {
)}
);
-};
+}
const Wrapper = styled.section<{
width: 'calc(100vw - 40px)' | 'calc(372px - 40px)';
@@ -162,19 +133,16 @@ const Wrapper = styled.section<{
display: flex;
flex-direction: column;
width: ${({ width }) => width};
- margin: ${({ $selectedPinId }) => $selectedPinId === null && '0 auto'};
+ margin: 0 auto;
@media (max-width: 1076px) {
- width: calc(50vw - 40px);
+ width: ${({ $selectedPinId }) => ($selectedPinId ? '49vw' : '50vw')};
+ margin: ${({ $selectedPinId }) => $selectedPinId && '0'};
}
@media (max-width: 744px) {
width: 100%;
}
-
- @media (max-width: 372px) {
- width: ${({ width }) => width};
- }
`;
const PinDetailWrapper = styled.div`
@@ -202,7 +170,7 @@ const ToggleButton = styled.button<{
props.$isCollapsed &&
`
transform: rotate(180deg);
- top:45%;
+ top: 45%;
left: 372px;
z-index: 1;
`}
diff --git a/frontend/src/pages/UpdatedPinDetail.tsx b/frontend/src/pages/UpdatedPinDetail.tsx
index 59d8cc8eb..46069be70 100644
--- a/frontend/src/pages/UpdatedPinDetail.tsx
+++ b/frontend/src/pages/UpdatedPinDetail.tsx
@@ -1,15 +1,16 @@
+import { SetURLSearchParams } from 'react-router-dom';
+import styled from 'styled-components';
+
+import usePut from '../apiHooks/usePut';
+import { putApi } from '../apis/putApi';
+import Button from '../components/common/Button';
import Flex from '../components/common/Flex';
import Space from '../components/common/Space';
import Text from '../components/common/Text';
-import { putApi } from '../apis/putApi';
-import { SetURLSearchParams } from 'react-router-dom';
-import { ModifyPinFormProps } from '../types/tmap';
import InputContainer from '../components/InputContainer';
-import { hasErrorMessage, hasNullValue } from '../validations';
import useToast from '../hooks/useToast';
-import Button from '../components/common/Button';
-import styled from 'styled-components';
-import usePut from '../apiHooks/usePut';
+import { ModifyPinFormProps } from '../types/FormValues';
+import { hasErrorMessage, hasNullValue } from '../validations';
interface UpdatedPinDetailProps {
searchParams: URLSearchParams;
@@ -26,7 +27,7 @@ interface UpdatedPinDetailProps {
) => void;
}
-const UpdatedPinDetail = ({
+function UpdatedPinDetail({
searchParams,
pinId,
formValues,
@@ -35,7 +36,7 @@ const UpdatedPinDetail = ({
setIsEditing,
updatePinDetailAfterEditing,
onChangeInput,
-}: UpdatedPinDetailProps) => {
+}: UpdatedPinDetailProps) {
const { showToast } = useToast();
const { fetchPut } = usePut();
@@ -46,7 +47,7 @@ const UpdatedPinDetail = ({
};
const onClickUpdatePin = async () => {
- if (hasErrorMessage(errorMessages) || hasNullValue(formValues, 'images')) {
+ if (hasErrorMessage(errorMessages) || hasNullValue(formValues)) {
showToast('error', '입력하신 항목들을 다시 한 번 확인해주세요.');
return;
}
@@ -72,33 +73,12 @@ const UpdatedPinDetail = ({
return (
-
-
-
- + 사진을 추가해주시면 더 알찬 정보를 제공해줄 수 있을 것 같아요.
-
-
-
);
-};
+}
const Wrapper = styled.div`
margin: 0 auto;
-
+ width: 100%;
@media (max-width: 1076px) {
width: calc(50vw - 40px);
}
diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx
index 7f5833bd5..dd9b8268a 100644
--- a/frontend/src/router.tsx
+++ b/frontend/src/router.tsx
@@ -1,10 +1,11 @@
-import { Suspense, lazy } from 'react';
+import { lazy, ReactNode, Suspense } from 'react';
import { createBrowserRouter } from 'react-router-dom';
-import Home from './pages/Home';
-import RootPage from './pages/RootPage';
-import { ReactNode } from 'react';
+
import AuthLayout from './components/Layout/AuthLayout';
+import Home from './pages/Home';
import NotFound from './pages/NotFound';
+import RootPage from './pages/RootPage';
+import Search from './pages/Search';
const SelectedTopic = lazy(() => import('./pages/SelectedTopic'));
const NewPin = lazy(() => import('./pages/NewPin'));
@@ -13,10 +14,10 @@ const SeeAllPopularTopics = lazy(() => import('./pages/SeeAllPopularTopics'));
const SeeAllNearTopics = lazy(() => import('./pages/SeeAllNearTopics'));
const SeeAllLatestTopics = lazy(() => import('./pages/SeeAllLatestTopics'));
const KakaoRedirect = lazy(() => import('./pages/KakaoRedirect'));
-const SeeTogetherTopics = lazy(() => import('./pages/SeeTogetherTopics'));
const Profile = lazy(() => import('./pages/Profile'));
const AskLogin = lazy(() => import('./pages/AskLogin'));
const Bookmark = lazy(() => import('./pages/Bookmark'));
+const SeeTogether = lazy(() => import('./pages/SeeTogether'));
interface routeElement {
path: string;
@@ -31,9 +32,9 @@ interface SuspenseCompProps {
children: ReactNode;
}
-const SuspenseComp = ({ children }: SuspenseCompProps) => {
+function SuspenseComp({ children }: SuspenseCompProps) {
return {children};
-};
+}
const routes: routeElement[] = [
{
@@ -102,15 +103,6 @@ const routes: routeElement[] = [
),
withAuth: false,
},
- {
- path: 'see-together',
- element: (
-
-
-
- ),
- withAuth: true,
- },
{
path: 'favorite',
element: (
@@ -147,6 +139,20 @@ const routes: routeElement[] = [
),
withAuth: false,
},
+ {
+ path: '/search',
+ element: ,
+ withAuth: false,
+ },
+ {
+ path: '/see-together/:topicId',
+ element: (
+
+
+
+ ),
+ withAuth: false,
+ },
],
},
];
diff --git a/frontend/src/setupTests.ts b/frontend/src/setupTests.ts
index fb075956f..ba3b1cc32 100644
--- a/frontend/src/setupTests.ts
+++ b/frontend/src/setupTests.ts
@@ -1,7 +1,3 @@
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/react/cleanup-after-each';
-
-// module.exports = {
-// testEnvironment: 'jsdom',
-// };
diff --git a/frontend/src/store/mapInstance.ts b/frontend/src/store/mapInstance.ts
new file mode 100644
index 000000000..27b8f5544
--- /dev/null
+++ b/frontend/src/store/mapInstance.ts
@@ -0,0 +1,13 @@
+import { create } from 'zustand';
+
+interface MapState {
+ mapInstance: TMap | null;
+ setMapInstance: (instance: TMap) => void;
+}
+
+const useMapStore = create((set) => ({
+ mapInstance: null,
+ setMapInstance: (instance: TMap) => set(() => ({ mapInstance: instance })),
+}));
+
+export default useMapStore;
diff --git a/frontend/src/themes/index.ts b/frontend/src/themes/index.ts
index f6e43ce28..d63b572a3 100644
--- a/frontend/src/themes/index.ts
+++ b/frontend/src/themes/index.ts
@@ -1,8 +1,8 @@
import color from './color';
import fontSize from './fontSize';
-import spacing from './spacing';
-import radius from './radius';
import fontWeight from './fontWeight';
+import radius from './radius';
+import spacing from './spacing';
const theme = {
fontSize,
diff --git a/frontend/src/types/Comment.ts b/frontend/src/types/Comment.ts
new file mode 100644
index 000000000..bc6c5d1f3
--- /dev/null
+++ b/frontend/src/types/Comment.ts
@@ -0,0 +1,9 @@
+interface Comment {
+ id: number;
+ content: string;
+ creator: string;
+ creatorImageUrl: string;
+ parentPinCommentId: number | null;
+ canChange: boolean;
+ updatedAt: String;
+}
diff --git a/frontend/src/types/Map.ts b/frontend/src/types/Map.ts
index ba5b370a8..d03b56d35 100644
--- a/frontend/src/types/Map.ts
+++ b/frontend/src/types/Map.ts
@@ -1,7 +1,3 @@
-export interface MapAddressProps {
- addressInfo: AddressInfoProps;
-}
-
export interface AddressInfoProps {
addressType: string;
adminDong: string;
@@ -21,45 +17,6 @@ export interface AddressInfoProps {
roadName: string;
}
-export interface MapProps {
- isMobile: boolean;
- mouseClickFlag: boolean;
- name: string;
- _data: MapDataProps;
- _object_: MapObjectProps;
- _status: MapStatusProps;
-}
-
-export interface MapDataProps {
- mapType: number;
- maxBounds: {};
- target: string;
- container: {};
- vsmMap: {};
- vsmOptions: {};
- minZoomLimit: number;
- maxZoomLimit: number;
- options: MapOptionsProps;
-}
-
-export interface MapObjectProps {
- eventListeners: {};
- getHandlers: string;
- fireEvent: string;
-}
-
-export interface MapStatusProps {
- zoom: number;
- center: {};
- width: number;
- height: number;
-}
-
-export interface MapOptionsProps {
- draggable: boolean;
- measureControl: boolean;
- naviControl: boolean;
- pinchZoom: boolean;
- scaleBar: boolean;
- scrollwheel: boolean;
+export interface MapAddressProps {
+ addressInfo: AddressInfoProps;
}
diff --git a/frontend/src/types/Pin.ts b/frontend/src/types/Pin.ts
index e226333a5..06c6dd236 100644
--- a/frontend/src/types/Pin.ts
+++ b/frontend/src/types/Pin.ts
@@ -1,3 +1,8 @@
+export interface ImageProps {
+ id: number;
+ imageUrl: string;
+}
+
export interface PinProps {
id: number;
name: string;
@@ -10,8 +15,3 @@ export interface PinProps {
updatedAt: string;
images: ImageProps[];
}
-
-export interface ImageProps {
- id: number;
- imageUrl: string;
-}
\ No newline at end of file
diff --git a/frontend/src/types/Poi.ts b/frontend/src/types/Poi.ts
new file mode 100644
index 000000000..470b9135d
--- /dev/null
+++ b/frontend/src/types/Poi.ts
@@ -0,0 +1,77 @@
+export interface EvCharger {
+ evCharger: any[];
+}
+
+export interface NewAddress {
+ centerLat: string;
+ centerLon: string;
+ frontLat: string;
+}
+
+export interface NewAddressList {
+ newAddress: NewAddress[];
+}
+
+export interface Poi {
+ id: string;
+ pkey: string;
+ navSeq: string;
+ collectionType: string;
+ name: string;
+
+ adminDongCode: string;
+ bizName: string;
+ dataKind: string;
+ desc: string;
+
+ evChargers: EvCharger;
+ firstBuildNo: string;
+ firstNo: string;
+ frontLat: string;
+ frontLon: string;
+
+ legalDongCode: string;
+
+ lowerAddrName: string;
+
+ middleAddrName: string;
+ mlClass: string;
+
+ newAddressList: NewAddressList;
+
+ noorLat: string;
+ noorLon: string;
+
+ parkFlag: string;
+
+ radius: string;
+
+ roadName: string;
+
+ rpFlag: string;
+
+ secondBuildNo: string;
+
+ secondNo: string;
+
+ telNo: string;
+
+ upperAddrName: string;
+
+ zipCode: String;
+}
+
+export interface Pois {
+ poi: Poi[];
+}
+
+export interface SearchPoiInfo {
+ totalCount: string;
+ count: string;
+ page: string;
+ pois: Pois;
+}
+
+export interface PoiApiResponse {
+ searchPoiInfo: SearchPoiInfo;
+}
diff --git a/frontend/src/types/index.d.ts b/frontend/src/types/index.d.ts
index 990e15785..6319b25a2 100644
--- a/frontend/src/types/index.d.ts
+++ b/frontend/src/types/index.d.ts
@@ -1,11 +1,13 @@
declare module '*.svg' {
import React from 'react';
+
const SVG: React.VFC>;
export default SVG;
}
-// declare global {
-// interface Window {
-// Tmapv3: Tmapv3;
-// }
-// }
+declare module '*.webp' {
+ import React from 'react';
+
+ const WEBP: string;
+ export default WEBP;
+}
diff --git a/frontend/src/types/tmap.d.ts b/frontend/src/types/tmap.d.ts
index df11ab3e5..4fc3b0ee0 100644
--- a/frontend/src/types/tmap.d.ts
+++ b/frontend/src/types/tmap.d.ts
@@ -1,23 +1,14 @@
-interface Window {
- Tmapv3: {
- Map: new (
- element: HTMLElement,
- options?: { center?: LatLng; scaleBar: boolean },
- ) => TMap;
- LatLng: new (lat: number, lng: number) => LatLng;
- LatLngBounds: new () => LatLngBounds;
- Marker: new (options?: MarkerOptions) => Marker;
- InfoWindow: new (options?: InfoWindowOptions) => InfoWindow;
- Point: new (x: number, y: number) => Point;
- };
- daum: any;
+interface LatLng {}
+
+interface LatLngBounds {
+ extend(latLng: LatLng): void;
}
interface evt {
data: {
lngLat: {
- _lat: number;
- _lng: number;
+ lat: number;
+ lng: number;
};
};
}
@@ -29,14 +20,10 @@ interface TMap {
fitBounds(bounds: LatLngBounds): void;
setCenter(latLng: LatLng): void;
setZoom(zoomLevel: number): void;
- on(eventType: string, callback: (evt: evt) => void): void;
- removeListener(eventType: string, callback: (evt: evt) => void): void;
-}
-
-interface LatLng {}
-
-interface LatLngBounds {
- extend(latLng: LatLng): void;
+ getZoom(): number;
+ on(eventType: string, callback: (event: evt) => void): void;
+ removeListener(eventType: string, callback: (event: evt) => void): void;
+ resize(width: number, height: number): void;
}
interface Marker {
@@ -45,7 +32,7 @@ interface Marker {
map?: Map;
id?: string;
getPosition(): LatLng;
- on(eventType: string, callback: (evt: Event) => void): void;
+ on(eventType: string, callback: (event: evt) => void): void;
setMap(mapOrNull?: Map | null): void;
}
@@ -66,3 +53,18 @@ interface InfoWindow {
open(map?: Map, marker?: Marker, latlng?: LatLng): void;
close(): void;
}
+
+interface Window {
+ Tmapv3: {
+ Map: new (
+ element: HTMLElement,
+ options?: { center?: LatLng; scaleBar: boolean },
+ ) => TMap;
+ LatLng: new (lat: number, lng: number) => LatLng;
+ LatLngBounds: new () => LatLngBounds;
+ Marker: new (options?: MarkerOptions) => Marker;
+ InfoWindow: new (options?: InfoWindowOptions) => InfoWindow;
+ Point: new (x: number, y: number) => Point;
+ };
+ daum: any;
+}
diff --git a/frontend/src/utils/RouteChangeTracker.tsx b/frontend/src/utils/RouteChangeTracker.tsx
index 2036ea431..f3831cb1a 100644
--- a/frontend/src/utils/RouteChangeTracker.tsx
+++ b/frontend/src/utils/RouteChangeTracker.tsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
-import { useLocation } from 'react-router-dom';
import ReactGA from 'react-ga4';
+import { useLocation } from 'react-router-dom';
const RouteChangeTracker = () => {
const location = useLocation();
diff --git a/frontend/src/utils/getCurrentLocation.ts b/frontend/src/utils/getCurrentLocation.ts
index 7f99f394b..7e069773c 100644
--- a/frontend/src/utils/getCurrentLocation.ts
+++ b/frontend/src/utils/getCurrentLocation.ts
@@ -1,7 +1,7 @@
const getCurrentLocation = () => {
const onSuccess = (pos: GeolocationPosition) => {
- const latitude = pos.coords.latitude;
- const longitude = pos.coords.longitude;
+ const { latitude } = pos.coords;
+ const { longitude } = pos.coords;
};
const onFail = () => {
diff --git a/frontend/src/validations/index.ts b/frontend/src/validations/index.ts
index 8faab7d35..1c502deb4 100644
--- a/frontend/src/validations/index.ts
+++ b/frontend/src/validations/index.ts
@@ -3,26 +3,20 @@ const REG_EXP_CURSES =
const REG_EXP_POLITICALLY =
/(괴뢰|빨갱이|왜놈|일베|조센징|쪽바리|짱깨|월북|매국노|메갈|섹스|쎅쓰|쎅스|섹쓰)/;
-export const validateCurse = (userInput: string) => {
- return REG_EXP_CURSES.test(userInput);
-};
+export const validateCurse = (userInput: string) =>
+ REG_EXP_CURSES.test(userInput);
-export const validatePolitically = (userInput: string) => {
- return REG_EXP_POLITICALLY.test(userInput);
-};
+export const validatePolitically = (userInput: string) =>
+ REG_EXP_POLITICALLY.test(userInput);
-export const hasErrorMessage = (errorMessages: T) => {
- return Object.values(errorMessages).some(
- (errorMessage) => errorMessage.length > 0,
- );
-};
+export const hasErrorMessage = (errorMessages: T) =>
+ Object.values(errorMessages).some((errorMessage) => errorMessage.length > 0);
export const hasNullValue = (
formValues: T,
notRequiredKey?: keyof T,
-) => {
- return Object.entries(formValues).some(([key, value]) => {
+) =>
+ Object.entries(formValues).some(([key, value]) => {
if (notRequiredKey && key === notRequiredKey) return false;
return value.length === 0;
});
-};
diff --git a/frontend/webpack.common.js b/frontend/webpack.common.js
index 51c549b8b..d9286c098 100644
--- a/frontend/webpack.common.js
+++ b/frontend/webpack.common.js
@@ -1,7 +1,7 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ProvidePlugin, DefinePlugin } = require('webpack');
-const Dotenv = require('dotenv-webpack');
+const DotenvWebpackPlugin = require('dotenv-webpack');
module.exports = {
entry: {
@@ -24,6 +24,9 @@ module.exports = {
new DefinePlugin({
'process.env.APP_URL': JSON.stringify(process.env.APP_URL),
}),
+ new DotenvWebpackPlugin({
+ systemvars: true,
+ }),
],
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
@@ -42,7 +45,7 @@ module.exports = {
},
},
{
- test: /\.(png|jpe?g)$/,
+ test: /\.(png|jpe?g|webp)$/,
type: 'asset',
},
{