From ff18a099b052ec383a0827abc01cb21198f2189e Mon Sep 17 00:00:00 2001
From: ParkGeunCheol <72205402+GC-Park@users.noreply.github.com>
Date: Sat, 14 Oct 2023 03:22:43 +0900
Subject: [PATCH] =?UTF-8?q?[FE]=20Feature/#550=20=ED=86=A0=ED=94=BD=20?=
=?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?=
=?UTF-8?q?=ED=95=80=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD=EC=A0=9C=20?=
=?UTF-8?q?=EA=B8=B0=EB=8A=A5=20(#583)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: 핀 이미지 삭제 기능 구현
* feat: 토픽 사진 수정 기능 예비
* refactor: 핀 이미지 삭제 시 재렌더링 안되던 문제 해결
* refactor: 핀 수정 기능 명세 연결 및 구현
* refactor: 지도 사진 수정할 때 사진만 수정되도록 변경
* refactor: 함수 이름 변경 및 지도 사진 수정 타이틀 작성
* refactor: 지도 수정 버튼 위치 이동
---
frontend/src/apis/putApi.ts | 23 +++-
frontend/src/assets/remove_image_icon.svg | 9 ++
.../components/PinImageContainer/index.tsx | 85 ++++++++++----
.../components/TopicInfo/UpdatedTopicInfo.tsx | 110 +++++++++++++++++-
frontend/src/pages/PinDetail.tsx | 2 +-
5 files changed, 205 insertions(+), 24 deletions(-)
create mode 100644 frontend/src/assets/remove_image_icon.svg
diff --git a/frontend/src/apis/putApi.ts b/frontend/src/apis/putApi.ts
index bdcf46f3..e84978e7 100644
--- a/frontend/src/apis/putApi.ts
+++ b/frontend/src/apis/putApi.ts
@@ -4,12 +4,33 @@ import withTokenRefresh from './utils';
export const putApi = async (
url: string,
- payload: {},
+ payload: {} | FormData,
contentType?: ContentTypeType,
) => {
const data = await withTokenRefresh(async () => {
const apiUrl = `${DEFAULT_PROD_URL + url}`;
const userToken = localStorage.getItem('userToken');
+
+ if (payload instanceof FormData) {
+ const headers: any = {};
+
+ if (userToken) {
+ headers.Authorization = `Bearer ${userToken}`;
+ }
+
+ const response = await fetch(apiUrl, {
+ method: 'PUT',
+ headers,
+ body: payload,
+ });
+
+ if (response.status >= 400) {
+ throw new Error('[SERVER] POST 요청에 실패했습니다.');
+ }
+
+ return response;
+ }
+
const headers: any = {
'content-type': 'application/json',
};
diff --git a/frontend/src/assets/remove_image_icon.svg b/frontend/src/assets/remove_image_icon.svg
new file mode 100644
index 00000000..f4f48825
--- /dev/null
+++ b/frontend/src/assets/remove_image_icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/components/PinImageContainer/index.tsx b/frontend/src/components/PinImageContainer/index.tsx
index 5e25a276..fbc83065 100644
--- a/frontend/src/components/PinImageContainer/index.tsx
+++ b/frontend/src/components/PinImageContainer/index.tsx
@@ -2,34 +2,62 @@ import styled from 'styled-components';
import { ImageProps } from '../../types/Pin';
import Image from '../common/Image';
+import RemoveImageButton from '../../assets/remove_image_icon.svg';
+import useDelete from '../../apiHooks/useDelete';
+import useToast from '../../hooks/useToast';
interface PinImageContainerProps {
images: ImageProps[];
-}
+ getPinData: () => void;
+
+}const NOT_FOUND_IMAGE =
+'https://dr702blqc4x5d.cloudfront.net/2023-map-be-fine/icon/notFound_image.svg';
-const NOT_FOUND_IMAGE =
- 'https://dr702blqc4x5d.cloudfront.net/2023-map-be-fine/icon/notFound_image.svg';
+const PinImageContainer = ({ images, getPinData }: PinImageContainerProps) => {
+ const { fetchDelete } = useDelete();
+ const { showToast } = useToast();
-function PinImageContainer({ images }: PinImageContainerProps) {
+ const onRemovePinImage = (imageId: number) => {
+ const isRemoveImage = confirm('해당 이미지를 삭제하시겠습니까?');
+
+ if (isRemoveImage) {
+ fetchDelete({
+ url: `/pins/images/${imageId}`,
+ errorMessage: '이미지 제거에 실패했습니다.',
+ isThrow: true,
+ onSuccess: () => {
+ showToast('info', '핀에서 이미지가 삭제 되었습니다.');
+ getPinData();
+ },
+ });
+ }
+ };
return (
-
- {images.map(
- (image, index) =>
- index < 3 && (
-
-
-
- ),
- )}
-
+ <>
+
+ {images.map(
+ (image, index) =>
+ index < 3 && (
+
+
+ onRemovePinImage(image.id)}
+ >
+
+
+
+ ),
+ )}
+
+ >
);
-}
+};
const FilmList = styled.ul`
width: 330px;
@@ -38,7 +66,22 @@ const FilmList = styled.ul`
`;
const ImageWrapper = styled.li`
+ position: relative;
margin-right: 10px;
`;
+const RemoveImageIconWrapper = styled.div`
+ opacity: 0.6;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ line-height: 0;
+ background-color: #ffffff;
+ cursor: pointer;
+
+ &:hover {
+ opacity: 1;
+ }
+`;
+
export default PinImageContainer;
diff --git a/frontend/src/components/TopicInfo/UpdatedTopicInfo.tsx b/frontend/src/components/TopicInfo/UpdatedTopicInfo.tsx
index 91b70f0e..930a089a 100644
--- a/frontend/src/components/TopicInfo/UpdatedTopicInfo.tsx
+++ b/frontend/src/components/TopicInfo/UpdatedTopicInfo.tsx
@@ -12,6 +12,11 @@ import Button from '../common/Button';
import Flex from '../common/Flex';
import Space from '../common/Space';
import InputContainer from '../InputContainer';
+import Text from '../common/Text';
+import useCompressImage from '../../hooks/useCompressImage';
+import Image from '../common/Image';
+import { DEFAULT_TOPIC_IMAGE } from '../../constants';
+import { putApi } from '../../apis/putApi';
interface UpdatedTopicInfoProp {
id: number;
@@ -51,6 +56,8 @@ function UpdatedTopicInfo({
const [isPrivate, setIsPrivate] = useState(false); // 혼자 볼 지도 : 같이 볼 지도
const [isAllPermissioned, setIsAllPermissioned] = useState(true); // 모두 : 지정 인원
const [authorizedMemberIds, setAuthorizedMemberIds] = useState([]);
+ const [changedImages, setChangedImages] = useState(null);
+ const { compressImage } = useCompressImage();
const updateTopicInfo = async () => {
try {
@@ -123,8 +130,60 @@ function UpdatedTopicInfo({
);
}, []);
+ const onTopicImageFileChange = async (
+ event: React.ChangeEvent,
+ ) => {
+ const file = event.target.files && event.target.files[0];
+ const formData = new FormData();
+
+ if (!file) {
+ showToast(
+ 'error',
+ '이미지를 선택하지 않았거나 추가하신 이미지를 찾을 수 없습니다. 다시 선택해 주세요.',
+ );
+ return;
+ }
+
+ const compressedFile = await compressImage(file);
+ formData.append('image', compressedFile);
+
+ await putApi(`/topics/images/${id}`, formData);
+
+ const updatedImageUrl = URL.createObjectURL(compressedFile);
+ setChangedImages(updatedImageUrl);
+ };
+
return (
+
+
+ 지도 사진
+
+
+ 지도를 대표하는 사진을 변경할 수 있습니다.
+
+
+ ) => {
+ e.currentTarget.src = DEFAULT_TOPIC_IMAGE;
+ }}
+ />
+ 수정
+
+
+
+
+
theme.color.black};
+ background-color: ${({ theme }) => theme.color.lightGray};
+
+ font-size: ${({ theme }) => theme.fontSize.extraSmall};
+ font-weight: ${({ theme }) => theme.fontWeight.bold};
+ text-align: center;
+
+ border-radius: ${({ theme }) => theme.radius.small};
+ cursor: pointer;
+
+ position: absolute;
+ right: 5px;
+ bottom: -5px;
+
+ box-shadow: rgba(0, 0, 0, 0.3) 0px 0px 5px 0px;
+
+ &:hover {
+ filter: brightness(0.95);
+ }
+
+ @media (max-width: 372px) {
+ width: 40px;
+ height: 30px;
+
+ font-size: 8px;
+ }
+`;
+
+const ImageInputButton = styled.input`
+ display: none;
+`;
+
+const TopicImage = styled(Image)`
+ border-radius: ${({ theme }) => theme.radius.medium};
+`;
export default UpdatedTopicInfo;
+function compressImage(file: File) {
+ throw new Error('Function not implemented.');
+}
diff --git a/frontend/src/pages/PinDetail.tsx b/frontend/src/pages/PinDetail.tsx
index ef5f2179..f4f43ba1 100644
--- a/frontend/src/pages/PinDetail.tsx
+++ b/frontend/src/pages/PinDetail.tsx
@@ -175,7 +175,7 @@ function PinDetail({
onChange={onPinImageFileChange}
/>
-
+