Skip to content

Commit

Permalink
[FE] Feature/#304 모아보기 구현 (#307)
Browse files Browse the repository at this point in the history
* refactor: 태그 전역상태 생성 및 핀 이름이 같을 경우 동시 삭제되던 오류 수정

* feat: SeveralSelectedTopics 페이지 구현

제 야무진 뻘짓을 기록합니다.... 혹시나 나중에 이 방식으로 쓰인다면 참고자료가 되길....

* refactor: 모아보기 API 명세에 맞게 GET 기능 구현

제 뻘짓을 만회합니다

* remove: 사용하지 않는 페이지 제거

* feat: 모아보기 버튼 클릭 시 모아보기에 추가 및 상태 표시 아이콘 추가

* design: 모아보기 카운터 UI 재배치

* feat: 모아보기 정책에 맞게 7개까지만 모아볼 수 있도록 변경

* feat: 모아보기 및 즐겨찾기 not focused 버튼 SVG 추가

* refactor: props drilling 으로 인한 불필요한 interface 제거

* remove: 불필요한 페이지 컴포넌트 제거

* design: 모아보기 추가 카운터 애니메이션 구현

* refactor: 불필요한 console.log 제거

* fix: id 타입 에러 수정

* refactor: 불필요한 fragment 제거

* style: 불필요한 개행 제거

* feat: 모아보기 토픽을 한번에 삭제하는 기능 추가

* design: 토스트 위치 수정 및 애니메이션 변경

* rename: SVG 파일 변경

* feat: delete API 추가

* refactor: 토픽 타입 변경으로 인한 수정 및 기능 추가

* refactor: post content type 추가

* refactor: 불필요한 console.log 제거

* refactor: 중복 로직 제거 및 메서드명 변경

* refactor: 기존 메서트 사용 및 불필요한 import 제거

* fix: topicCard 타입 변경된 인터페이스 적용

* feat: 로그인 오류 발생 시 toast 띄우는 에러핸들링 추가
  • Loading branch information
semnil5202 authored Aug 16, 2023
1 parent 08b9f2c commit 980748e
Show file tree
Hide file tree
Showing 36 changed files with 581 additions and 287 deletions.
9 changes: 9 additions & 0 deletions frontend/src/apis/deleteApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const deleteApi = async (url: string, contentType?: string) => {
await fetch(`${process.env.REACT_APP_API_DEFAULT + url}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${localStorage.getItem('userToken') || ''}`,
'Content-Type': contentType || 'application/json',
},
});
};
8 changes: 5 additions & 3 deletions frontend/src/apis/postApi.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export const postApi = (url: string, data: {}) =>
fetch(`${process.env.REACT_APP_API_DEFAULT + url}`, {
export const postApi = async (url: string, data?: {}, contentType?: string) => {
await fetch(`${process.env.REACT_APP_API_DEFAULT + url}`, {
method: 'POST',
headers: {
'Content-type': 'application/json',
'Content-type': `${contentType || 'application/json'}`,
Authorization: `Bearer ${localStorage.getItem('userToken') || ''}`,
},
body: JSON.stringify(data),
});
};
4 changes: 4 additions & 0 deletions frontend/src/assets/favoriteBtn_notFilled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions frontend/src/assets/seeTogetherBtn_notFilled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/assets/topicInfo_favoriteBtn_fiiled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
4 changes: 4 additions & 0 deletions frontend/src/assets/topicInfo_seeTogetherBtn_filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 56 additions & 7 deletions frontend/src/components/AddSeeTogether/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,76 @@
import { styled } from 'styled-components';
import { postApi } from '../../apis/postApi';
import useToast from '../../hooks/useToast';
import { useState } from 'react';
import { useContext } from 'react';
import { getApi } from '../../apis/getApi';
import { TopicType } from '../../types/Topic';
import { SeeTogetherContext } from '../../context/SeeTogetherContext';
import { deleteApi } from '../../apis/deleteApi';

interface AddSeeTogetherProps {
isInAtlas: boolean;
id: number;
children: React.ReactNode;
setTopicsFromServer: () => void;
}

const AddSeeTogether = ({ id, children }: AddSeeTogetherProps) => {
const AddSeeTogether = ({
isInAtlas,
id,
children,
setTopicsFromServer,
}: AddSeeTogetherProps) => {
const { showToast } = useToast();
const { seeTogetherTopics, setSeeTogetherTopics } =
useContext(SeeTogetherContext);

const addSeeTogetherList = async (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();

// TODO : post 후 전역 see together List 에 담기, toast 메세지 수정
// await postApi('',{});
// await getApi('');
try {
if (seeTogetherTopics.length === 7) {
showToast('warning', '모아보기는 7개까지만 가능합니다.');
return;
}

showToast('info', '준비중인 기능입니다.');
await postApi(`/atlas/topics?id=${id}`, {}, 'x-www-form-urlencoded');

const topics = await getApi<TopicType[]>('default', '/members/my/atlas');

setSeeTogetherTopics(topics);

// TODO: 모아보기 페이지에서는 GET /members/my/atlas 두 번 됨
setTopicsFromServer();

showToast('info', '모아보기에 추가했습니다.');
} catch {
showToast(
'error',
'모아보기 추가에 실패했습니다. 로그인 후 사용해주세요.',
);
}
};

const deleteSeeTogether = async (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();

await deleteApi(`/atlas/topics?id=${id}`, 'x-www-form-urlencoded');

const topics = await getApi<TopicType[]>('default', '/members/my/atlas');

setSeeTogetherTopics(topics);

// TODO: 모아보기 페이지에서는 GET /members/my/atlas 두 번 됨
setTopicsFromServer();

showToast('info', '해당 지도를 모아보기에서 제외했습니다.');
};

return <Wrapper onClick={addSeeTogetherList}>{children}</Wrapper>;
return (
<Wrapper onClick={isInAtlas ? deleteSeeTogether : addSeeTogetherList}>
{children}
</Wrapper>
);
};

const Wrapper = styled.div`
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/Layout/AuthLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const AuthLayout = ({
let curAuth: boolean = getUserTokenInLocalStorage() === null ? false : true;

if (withAuth) {
console.log('withAuth');
if (!curAuth) routePage(to);
}

Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/Layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Modal from '../Modal';
import { ModalContext } from '../../context/ModalContext';
import { NavbarHighlightsContext } from '../../context/NavbarHighlightsContext';
import { useParams } from 'react-router-dom';
import SeeTogetherCounter from '../SeeTogetherCounter';

interface NavBarProps {
$layoutWidth: '100vw' | '372px';
Expand Down Expand Up @@ -84,6 +85,7 @@ const Navbar = ({ $layoutWidth }: NavBarProps) => {
>
모아보기
</Text>
<SeeTogetherCounter />
</IconWrapper>

<IconSpace size={7} $layoutWidth={$layoutWidth} />
Expand Down Expand Up @@ -158,6 +160,7 @@ const Wrapper = styled.nav<{ $layoutWidth: '100vw' | '372px' }>`
`;

const IconWrapper = styled.div`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
Expand Down
40 changes: 21 additions & 19 deletions frontend/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { LayoutWidthContext } from '../../context/LayoutWidthContext';
import SeeTogetherProvider from '../../context/SeeTogetherContext';
import Space from '../common/Space';
import Navbar from './Navbar';
import Back from '../../assets/Back.svg';
import ModalProvider from '../../context/ModalContext';
import NavbarHighlightsProvider from '../../context/NavbarHighlightsContext';
import TagProvider from '../../context/TagContext';
import InfoDefalutImg from '../../assets/InfoDefalutImg.svg';

type LayoutProps = {
Expand Down Expand Up @@ -58,13 +58,14 @@ const Layout = ({ children }: LayoutProps) => {
<CoordinatesProvider>
<MarkerProvider>
<SeeTogetherProvider>
<Flex height="100vh" width="100vw" overflow="hidden">
<LayoutFlex
$flexDirection="column"
$minWidth={width}
height="100vh"
$backgroundColor="white"
>
<TagProvider>
<Flex height="100vh" width="100vw" overflow="hidden">
<LayoutFlex
$flexDirection="column"
$minWidth={width}
height="100vh"
$backgroundColor="white"
>
<Flex
$justifyContent="space-between"
padding="20px 20px 0 20px"
Expand All @@ -78,17 +79,18 @@ const Layout = ({ children }: LayoutProps) => {
</Flex>
<Flex
height="calc(100vh - 40px)"
$flexDirection="column"
overflow="auto"
padding="0 20px 20px 20px"
>
{children}
</Flex>
<Navbar $layoutWidth={width} />
<Toast />
</LayoutFlex>
<Map ref={mapContainer} map={map} $minWidth={width} />
</Flex>
$flexDirection="column"
overflow="auto"
padding="0 20px 20px 20px"
>
{children}
</Flex>
<Navbar $layoutWidth={width} />
<Toast />
</LayoutFlex>
<Map ref={mapContainer} map={map} $minWidth={width} />
</Flex>
</TagProvider>
</SeeTogetherProvider>
</MarkerProvider>
</CoordinatesProvider>
Expand Down
31 changes: 11 additions & 20 deletions frontend/src/components/PinPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import Flex from '../common/Flex';
import Space from '../common/Space';
import Text from '../common/Text';
import useNavigator from '../../hooks/useNavigator';
import { useEffect, useRef, useState, KeyboardEvent } from 'react';
import { useEffect, useRef, useState, KeyboardEvent, useContext } from 'react';
import theme from '../../themes';
import Box from '../common/Box';
import { TagContext } from '../../context/TagContext';

export interface PinPreviewProps {
idx: number;
Expand All @@ -14,11 +15,7 @@ export interface PinPreviewProps {
pinInformation: string;
setSelectedPinId: React.Dispatch<React.SetStateAction<number | null>>;
pinId: number;
topicId: string | undefined;
tagPins: string[];
setTagPins: React.Dispatch<React.SetStateAction<string[]>>;
taggedPinIds: number[];
setTaggedPinIds: React.Dispatch<React.SetStateAction<number[]>>;
topicId: string;
setIsEditPinDetail: React.Dispatch<React.SetStateAction<boolean>>;
}

Expand All @@ -30,35 +27,28 @@ const PinPreview = ({
setSelectedPinId,
pinId,
topicId,
tagPins,
setTagPins,
taggedPinIds,
setTaggedPinIds,
setIsEditPinDetail,
}: PinPreviewProps) => {
const { routePage } = useNavigator();

const { tags, setTags } = useContext(TagContext);
const [announceText, setAnnounceText] = useState<string>('토픽 핀 선택');

const inputRef = useRef<HTMLInputElement | null>(null);

const onAddTagOfTopic = (e: React.ChangeEvent<HTMLInputElement>) => {
e.stopPropagation();

if (e.target.checked) {
setTagPins([...tagPins, pinTitle]);
setTaggedPinIds((prev) => [...prev, pinId]);
setTags((prevTags) => [...prevTags, { id: pinId, title: pinTitle }]);

if (taggedPinIds.length === 0) {
if (tags.length === 0) {
setAnnounceText(`핀 ${pinTitle}이 태그에 추가됨. 뽑아오기 기능 활성화`);
return;
}
setAnnounceText(`핀 ${pinTitle}이 태그에 추가됨`);
} else {
setTagPins(tagPins.filter((value) => value !== pinTitle));
setTaggedPinIds(taggedPinIds.filter((value) => value !== pinId));
setTags(tags.filter((tag) => tag.id !== pinId));

if (taggedPinIds.length === 1) {
if (tags.length === 1) {
setAnnounceText(
`핀 ${pinTitle}이 태그에서 삭제됨. 뽑아오기 기능 비활성화`,
);
Expand All @@ -71,6 +61,7 @@ const PinPreview = ({
const onClickSetSelectedPinId = () => {
setSelectedPinId(pinId);
setIsEditPinDetail(false);

routePage(`/topics/${topicId}?pinDetail=${pinId}`);
};

Expand All @@ -92,7 +83,7 @@ const PinPreview = ({

return (
<Flex
height="150px"
height="152px"
position="relative"
$justifyContent="space-between"
$backgroundColor="white"
Expand Down Expand Up @@ -131,7 +122,7 @@ const PinPreview = ({
type="checkbox"
onChange={onAddTagOfTopic}
onKeyDown={onInputKeyDown}
checked={Boolean(taggedPinIds.includes(pinId))}
checked={Boolean(tags.map((tag) => tag.id).includes(pinId))}
aria-label={`${pinTitle} 핀 선택`}
ref={inputRef}
/>
Expand Down
Loading

0 comments on commit 980748e

Please sign in to comment.