Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] Feature/#550 토픽 이미지 수정 및 핀 이미지 삭제 기능 #583

Merged
merged 7 commits into from
Oct 13, 2023
23 changes: 22 additions & 1 deletion frontend/src/apis/putApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/assets/remove_image_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 64 additions & 21 deletions frontend/src/components/PinImageContainer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<FilmList>
{images.map(
(image, index) =>
index < 3 && (
<ImageWrapper>
<Image
key={image.id}
height="100px"
width="100px"
src={image.imageUrl}
$errorDefaultSrc={NOT_FOUND_IMAGE}
/>
</ImageWrapper>
),
)}
</FilmList>
<>
<FilmList>
{images.map(
(image, index) =>
index < 3 && (
<ImageWrapper>
<Image
key={image.id}
height="100px"
width="100px"
src={image.imageUrl}
$errorDefaultSrc={NOT_FOUND_IMAGE}
/>
<RemoveImageIconWrapper
onClick={() => onRemovePinImage(image.id)}
>
<RemoveImageButton />
</RemoveImageIconWrapper>
</ImageWrapper>
),
)}
</FilmList>
</>
);
}
};

const FilmList = styled.ul`
width: 330px;
Expand All @@ -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;
110 changes: 109 additions & 1 deletion frontend/src/components/TopicInfo/UpdatedTopicInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,6 +56,8 @@ function UpdatedTopicInfo({
const [isPrivate, setIsPrivate] = useState(false); // 혼자 볼 지도 : 같이 볼 지도
const [isAllPermissioned, setIsAllPermissioned] = useState(true); // 모두 : 지정 인원
const [authorizedMemberIds, setAuthorizedMemberIds] = useState<number[]>([]);
const [changedImages, setChangedImages] = useState<string | null>(null);
const { compressImage } = useCompressImage();

const updateTopicInfo = async () => {
try {
Expand Down Expand Up @@ -123,8 +130,60 @@ function UpdatedTopicInfo({
);
}, []);

const onTopicImageFileChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
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 (
<Wrapper>
<ImageWrapper>
<Text color="black" $fontSize="default" $fontWeight="normal">
지도 사진
</Text>
<Text color="gray" $fontSize="small" $fontWeight="normal">
지도를 대표하는 사진을 변경할 수 있습니다.
</Text>
<Space size={0} />
<TopicImage
height="168px"
width="100%"
src={changedImages ? changedImages : image}
alt="사진 이미지"
$objectFit="cover"
onError={(e: React.SyntheticEvent<HTMLImageElement, Event>) => {
e.currentTarget.src = DEFAULT_TOPIC_IMAGE;
}}
/>
<ImageInputLabel htmlFor="file">수정</ImageInputLabel>
<ImageInputButton
id="file"
type="file"
name="image"
onChange={onTopicImageFileChange}
/>
</ImageWrapper>

<Space size={5} />

<InputContainer
tagType="input"
containerTitle="지도 이름"
Expand Down Expand Up @@ -185,6 +244,55 @@ function UpdatedTopicInfo({
);
}

const Wrapper = styled.section``;
const Wrapper = styled.article``;

const ImageWrapper = styled.div`
position: relative;
`;

const ImageInputLabel = styled.label`
width: 60px;
height: 35px;
margin-bottom: 10px;
padding: 10px 10px;
color: ${({ theme }) => 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.');
}
2 changes: 1 addition & 1 deletion frontend/src/pages/PinDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function PinDetail({
onChange={onPinImageFileChange}
/>

<PinImageContainer images={pin.images} />
<PinImageContainer images={pin.images} getPinData={getPinData} />

<Space size={6} />

Expand Down
Loading