Skip to content

Commit

Permalink
add : 버그제보 이미지 여러 장 설정
Browse files Browse the repository at this point in the history
버그제보 이미지 여러 장 설정
  • Loading branch information
phyuna0525 authored Aug 3, 2024
2 parents 5a81ff7 + 8e1cba5 commit a54f317
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 54 deletions.
10 changes: 6 additions & 4 deletions src/api/bug/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { useMutation } from "@tanstack/react-query";
import { instance } from "..";
import apiError from "@/hook/errorHandling";

interface BugProp {
title: string;
content: string;
file_name: string;
file_name: string[];
}

export const BugPost = () => {
Expand All @@ -28,11 +28,13 @@ export const BugPost = () => {

export const BugImg = () => {
const { handleError } = apiError();
return useMutation<string, Error, { file: File }>({
return useMutation<string[], Error, { file: File[] }>({
mutationFn: async (param) => {
try {
const formData = new FormData();
formData.append("file", param.file);
param.file.forEach((file) => {
formData.append("file", file);
});
const result = await instance.post(`/bug/upload`, formData);
return result.data;
} catch (error) {
Expand Down
34 changes: 18 additions & 16 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,32 @@ refreshInstance.interceptors.request.use(
},
(error: AxiosError) => Promise.reject(error)
);

instance.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (axios.isAxiosError(error) && error.response) {
const { status } = error.response;
if (status === 401) {
const refreshToken = cookie.get("refresh_token");
if (refreshToken) {
try {
await axios
.put(`${BASEURL}/admin/refresh`, null, {
headers: {
"X-Refresh-Token": `${refreshToken}`,
},
})
.then((response) => {
const data = response.data;
cookie.set("access_token", data.access_token);
cookie.set("refresh_token", data.refresh_token);
});
} catch (refreshError) {
return Promise.reject(refreshError);
try {
await axios
.put(`${BASEURL}/admin/refresh`, null, {
headers: {
"X-Refresh-Token": `${refreshToken}`,
},
})
.then((response) => {
const data = response.data;
cookie.set("access_token", data.access_token);
cookie.set("refresh_token", data.refresh_token);
})
.catch(() => {
window.location.href = "login";
});
{
}
} catch (refreshError) {
return Promise.reject(refreshError);
}
}
}
Expand Down
81 changes: 50 additions & 31 deletions src/app/BugReport/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import Input from "@/components/input";
import TextArea from "@/components/input/textarea";
import Button from "@/components/button";
import { BugPost, BugImg } from "@/api/bug/index";
import ImgModal from "@/components/modal/imgModal";
import BugReportImg from "@/assets/svg/bugreport.svg";

interface bugProp {
title: string;
content: string;
file_name: string;
file_name: string[];
}

const BugReport = () => {
Expand All @@ -18,50 +20,40 @@ const BugReport = () => {
const [data, setData] = useState<bugProp>({
title: "",
content: "",
file_name: "",
file_name: [],
});
const [filename, setFilename] = useState<string>("");
const [modal, setModal] = useState<boolean>(false);

const handleContent = ({ text, name }: { text: string; name: string }) => {
setData({ ...data, [name]: text });
};

const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.currentTarget.files?.[0];
if (selectedFile) {
try {
await BugImgMutate(
{ file: selectedFile },
{
onSuccess: (data) => {
setFilename(data);
},
onError: (error) => {
alert(error.message);
},
}
);
} catch (error) {
console.log(error);
}
const handleImgUpload = async (images: File[]) => {
try {
await BugImgMutate(
{ file: images },
{
onSuccess: (data) => {
setData((prevData) => ({
...prevData,
file_name: data,
}));
},
}
);
} catch (error) {
console.error("Upload failed:", error);
}
};

useEffect(() => {
setData({
...data,
file_name: filename,
});
}, [filename]);

const Bug = async () => {
await BugPostMutate(data, {
onSuccess: () => {
alert("버그가 제보되었습니다.");
setData({
title: "",
content: "",
file_name: filename ? filename : "",
file_name: [],
});
},
onError: () => {
Expand Down Expand Up @@ -98,9 +90,36 @@ const BugReport = () => {
/>
</div>
<div className="flex flex-col gap-2">
<div className="text-sub-title4-M">버그 사진을 첨부해주세요</div>
<input type="file" onChange={handleFileChange} />
<div className="mb-11">
<>
<p>버그 사진을 첨부해주세요</p>
<label
htmlFor="file-input"
className="cursor-pointer flex flex-col p-8 justify-center items-center w-full h-max rounded-md bg-neutral-900 text-gray-500 mb-9"
onClick={() => {
setModal(true);
}}
>
<img src={BugReportImg.src} alt="bug report icon" />
<p>사진을 첨부해주세요</p>
</label>
<div
id="file-input"
className="hidden"
onChange={() => {
setModal(!modal);
}}
/>
</>
</div>
</div>
<ImgModal
onClick={handleImgUpload}
isOpen={modal}
onClose={() => {
setModal(!modal);
}}
/>
</div>
<Button onClick={Bug} buttonSize="full" colorType="primary">
버그 제보하기
Expand Down
3 changes: 3 additions & 0 deletions src/assets/svg/bugreport.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
153 changes: 153 additions & 0 deletions src/components/modal/imgModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { useState, useRef } from "react";

interface ModalProps {
isOpen: boolean;
onClose: () => void;
onClick: (images: File[]) => void;
}

const ImgModal: React.FC<ModalProps> = ({ isOpen, onClose, onClick }) => {
const [dragActive, setDragActive] = useState(false);
const [images, setImages] = useState<File[]>([]);
const pasteDivRef = useRef<HTMLDivElement>(null);

if (!isOpen) return null;

const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragActive(true);
};

const handleDragLeave = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
};

const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);

const files = Array.from(e.dataTransfer.files);
if (files.length > 0) {
setImages((prevImages) => [...prevImages, ...files]);
}
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (files.length > 0) {
setImages((prevImages) => [...prevImages, ...files]);
}
};

const handlePaste = (e: React.ClipboardEvent<HTMLDivElement>) => {
e.preventDefault();
const items = Array.from(e.clipboardData.items);
const pastedFiles = items
.filter((item) => item.kind === "file" && item.type.startsWith("image/"))
.map((item) => item.getAsFile())
.filter((file): file is File => file !== null);

if (pastedFiles.length > 0) {
setImages((prevImages) => [...prevImages, ...pastedFiles]);
}

if (pasteDivRef.current) {
pasteDivRef.current.innerHTML = "";
}
};

const handleRemoveImage = (index: number) => {
setImages((prevImages) => prevImages.filter((_, i) => i !== index));
};

const onCancle = () => {
onClose();
setImages([]);
};

const AddImg = () => {
onClick(images);
setImages([]);
onClose();
};

return (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md flex flex-col items-end">
<button className=" text-2xl mb-3" onClick={onClose}>
&times;
</button>
<label
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`border-2 border-dashed border-gray-300 rounded-lg p-8 w-full flex flex-col items-center bg-gray-200 ${
dragActive ? "bg-gray-300" : ""
}`}
htmlFor="imgUpload"
>
<div className="text-4xl mb-4">&#128247;</div>
<span>이미지를 끌어오거나 업로드 할 파일을 선택해 주세요</span>
<input
type="file"
id="imgUpload"
className="hidden"
onChange={handleChange}
multiple
/>
</label>
<div className="my-4 relative w-full text-center">
<span className="bg-white px-2 absolute -top-3 z-10 left-40% text-gray-300 ">
또는
</span>
<div className="absolute left-0 top-1/2 w-full h-px bg-gray-300 transform -translate-y-1/2"></div>
</div>
<div
ref={pasteDivRef}
contentEditable="true"
onPaste={handlePaste}
className="border border-gray-300 rounded-lg w-full p-2 mb-4"
>
이미지를 붙여넣기 해주세요.
</div>
<div className="my-4 w-full grid grid-cols-3 gap-2">
{images.map((image, index) => (
<div key={index} className="relative w-25 h-25 overflow-hidden">
<img
src={URL.createObjectURL(image)}
alt={`Uploaded ${index}`}
className="min-w-full min-h-full rounded-lg"
/>
<button
onClick={() => handleRemoveImage(index)}
className="absolute top-1 right-1 text-white bg-black bg-opacity-50 rounded-full px-2 py-1"
>
&times;
</button>
</div>
))}
</div>
<div className="flex gap-2">
<button
className="bg-gray-300 text-white py-2 px-4 rounded-lg"
onClick={onCancle}
>
취소
</button>
<button
className="bg-purple-600 text-white py-2 px-4 rounded-lg"
onClick={AddImg}
>
추가
</button>
</div>
</div>
</div>
);
};

export default ImgModal;
4 changes: 1 addition & 3 deletions src/hook/errorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ const apiError = () => {
alert("400 잘못된 요청입니다");
};

const handle401: ErrorHandler = () => {
router.push("login");
};
const handle401: ErrorHandler = () => {};

const handle403: ErrorHandler = () => {
alert("403 권한이 없습니다");
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ const config: Config = {
},
spacing: {
"4%": "4%",
"40%": "44%",
},
},
},
Expand Down

0 comments on commit a54f317

Please sign in to comment.