diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a39d5e..919eace 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,11 +30,9 @@ dependencies: specifier: ^8.9.2 version: 8.9.2 react: - specifier: ^18.2.0 specifier: ^18.2.0 version: 18.2.0 react-dom: - specifier: ^18.2.0 specifier: ^18.2.0 version: 18.2.0(react@18.2.0) react-icons: diff --git a/src/api/service.ts b/src/api/service.ts index a693548..3200d1a 100644 --- a/src/api/service.ts +++ b/src/api/service.ts @@ -32,7 +32,7 @@ export const postProducts = async ( formData.append('title', title); formData.append('categoryName', categoryName); formData.append('content', content); - formData.append('price', price.toString()); + formData.append('price', price); // 여러 이미지를 처리하는 경우 if (images) { @@ -51,11 +51,47 @@ export const deleteProducts = async (id: number) => { return res; }; +// 상품수정 +export const putProducts = async ( + id: number, + title: string, + categoryName: string, + content: string, + price: string, + images?: FileList | string[] | null, +) => { + const formData = new FormData(); + formData.append('title', title); + formData.append('categoryName', categoryName); + formData.append('content', content); + formData.append('price', price); + + // 여러 이미지를 처리하는 경우 + if (images) { + for (let i = 0; i < images.length; i++) { + formData.append('images', images[i]); + } + } + + const config = { + headers: { + 'Content-Type': 'multipart/form-data', + }, + params: { + productId: id, + }, + }; + + const res = await client.put(`products/${id}`, formData, config); + return res; +}; + export const getProductCategory = async () => { const res = await client.get(`/products/categories`); return res.data; }; +// 상품정보 불러오기 export const getProducts = async (searchWord?: string, category?: string) => { const res = await client.get('/products?pageSize=100', { params: { diff --git a/src/app/product/[id]/edit/page.tsx b/src/app/product/[id]/edit/page.tsx new file mode 100644 index 0000000..4464d90 --- /dev/null +++ b/src/app/product/[id]/edit/page.tsx @@ -0,0 +1,9 @@ +import ProductPut from '@/templates/product/productPut'; + +export default function Page() { + return ( + <> + + + ); +} diff --git a/src/templates/product/productDetail.tsx b/src/templates/product/productDetail.tsx index 796598e..3ed12c2 100644 --- a/src/templates/product/productDetail.tsx +++ b/src/templates/product/productDetail.tsx @@ -19,6 +19,7 @@ import 'slick-carousel/slick/slick-theme.css'; import 'slick-carousel/slick/slick.css'; import ProductDelete from './productDelete'; +import ProductPut from './productPut'; type Seller = { sellerId: number; @@ -173,9 +174,10 @@ export const ProductDetail = () => { role="button" ref={menuRef} className="product-detail__menu"> -
router.push('/product/edit')}> +
router.push(`/product/${id}/edit`)}> 게시글 수정
+
삭제
)} diff --git a/src/templates/product/productPut.tsx b/src/templates/product/productPut.tsx new file mode 100644 index 0000000..a81951c --- /dev/null +++ b/src/templates/product/productPut.tsx @@ -0,0 +1,236 @@ +'use client'; + +import '@/styles/templates/write/write.scss'; +import Header from '@/components/header'; +import { useState, useEffect } from 'react'; +import { putProducts, getProductDetail } from '@/api/service'; +import CategoryModal from '@/templates/write/categoryModal'; +import { useHandleImg } from '@/templates/write/useHandleImg'; +import Btn from '@/components/btn'; +import { useRouter, usePathname } from 'next/navigation'; +import { AXIOSResponse } from '@/types/interface'; + +export default function ProductPut() { + const [title, setTitle] = useState(''); + const [category, setCategory] = useState(''); + const [content, setContent] = useState(''); + const [price, setPrice] = useState(''); + const [isModal, setIsModal] = useState(false); + const [imgFiles, setImgFiles] = useState([]); + + type Product = { + id: number; + title: string; + price: string; + categoryName: string; + content: string; + images: string[]; + status: string; + likes: number; + myProduct: boolean; + seller: Seller; + sellerProductInfos: sellerProductInfos[]; + like: boolean; + }; + + const router = useRouter(); + const id = parseInt(usePathname().split('/')[2]); + + // const urlToFile = async ( + // url: string, + // filename: string, + // mimeType: string, + // ): Promise => { + // const res = await fetch(url, { mode: 'no-cors' }); + // const blob = await res.blob(); + // return new File([blob], filename, { type: mimeType }); + // }; + + useEffect(() => { + const fetchData = async () => { + try { + const res: AXIOSResponse = await getProductDetail(id); + if (res.statusCode === 200) { + const { + title = '', + categoryName = '', + content = '', + price = '', + } = res.data; + + setTitle(title); + setCategory(categoryName); + setContent(content); + setPrice(price); + const imageUrls = res.data.images; // 먼저 images를 가져온다 + setImgFiles(imageUrls); + // if (imageUrls) { + // const files = await Promise.all( + // imageUrls.map((url) => urlToFile(url, 'filename', 'image/*')), // filename과 mimeType은 적절하게 설정 + // ); + // setImgFiles(files); + // } + } + } catch (error: any) { + if (error.res) { + const errorData = error.res?.data; + console.error('get failed:', errorData); + } else { + console.error('An unexpected error occurred:', error); + } + } + }; + fetchData(); + }, [id]); + + const generateUniqueId = (image: File, index: number): string => { + return `${image.lastModified}-${image.name}-${index}`; + }; + + const toggleModal = () => { + setIsModal(!isModal); + }; + + const handleSelectCategory = (selectedCategory: string) => { + setCategory(selectedCategory); + }; + + // console.log('imgFiles', imgFiles); + const { imageArray, images, removeImage, handleImageChange } = + useHandleImg(imgFiles); + + const handleEdit = async () => { + try { + if (images) { + const res = await putProducts( + id, + title, + category, + content, + price, + images, + ); + if (res.data.statusCode === 200) { + router.push('/main'); + console.log('수정완료'); + } else { + console.error('Post failed:', res); + } + } + } catch (error: any) { + if (error.response) { + const errorData = error.response?.data; + console.error('Post failed:', errorData); + } else { + console.error('An unexpected error occurred:', error); + } + } + }; + + const imgCount = imageArray?.length; + + return ( + <> +
임시저장} + /> +
+
+
+
+ + {imageArray.map((image: any, index: number) => ( +
+ {`Uploaded +
removeImage(index)}> + x_btn +
+
+ ))} +
+
+ +

제목

+ setTitle(e.target.value)} + placeholder="제목 입력해주세요" + /> +
+

카테고리

+ + +
+

가격

+ { + const newValue = e.target.value; + // 숫자 또는 빈 문자열만 허용 + const isNumeric = /^[0-9]*$/.test(newValue); + if (isNumeric) { + setPrice(newValue); + } + }} + placeholder="가격을 입력해주세요" + /> +

자세한 설명

+ +
+
+ +
+
+ + ); +} diff --git a/src/templates/write/useHandleImg.ts b/src/templates/write/useHandleImg.ts index f510794..faa1bde 100644 --- a/src/templates/write/useHandleImg.ts +++ b/src/templates/write/useHandleImg.ts @@ -1,22 +1,67 @@ import { useState, useEffect } from 'react'; -export const useHandleImg = () => { - const [imageArray, setImageArray] = useState([]); +async function urlToFile( + url: string, + filename: string, + mimeType: string, +): Promise { + const res = await fetch(url, { mode: 'no-cors' }); + const buf = await res.arrayBuffer(); + return new File([buf], filename, { type: mimeType }); +} + +// const urlToFile = async ( +// url: string, +// filename: string, +// mimeType: string, +// ): Promise => { +// const res = await fetch(url, { mode: 'no-cors' }); +// const blob = await res.blob(); +// return new File([blob], filename, { type: mimeType }); +// }; + +export const useHandleImg = (initialFiles?: (File | string)[]) => { + const [imageArray, setImageArray] = useState<(File | string)[]>( + initialFiles || [], + ); const [images, setImages] = useState(null); + useEffect(() => { + if (initialFiles) { + const filePromises = initialFiles.map(async (item, index) => { + if (typeof item === 'string') { + return urlToFile(item, `file-${index}.jpg`, 'image/jpeg'); + } + return item; + }); + + Promise.all(filePromises).then((files) => { + setImageArray(files); + }); + } + }, [initialFiles]); + useEffect(() => { if (imageArray.length === 0) { setImages(null); return; } + const fileArray = imageArray.filter( + (item): item is File => item instanceof File, + ); + const dataTransfer = new DataTransfer(); - imageArray.forEach((file) => dataTransfer.items.add(file)); + fileArray.forEach((file) => dataTransfer.items.add(file)); setImages(dataTransfer.files); }, [imageArray]); const removeImage = (index: number) => { - URL.revokeObjectURL(URL.createObjectURL(imageArray[index])); + const target = imageArray[index]; + if (target instanceof File) { + URL.revokeObjectURL(URL.createObjectURL(target)); + } + const updatedImages = [...imageArray]; updatedImages.splice(index, 1); setImageArray(updatedImages);