From dd513da0b1f120ef08836907650047ad13cd3e6b Mon Sep 17 00:00:00 2001 From: cccwon2 Date: Tue, 24 Dec 2024 23:39:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20hello-pagea/dnd=20=EB=A1=9C=20d?= =?UTF-8?q?nd=20=EA=B5=90=EC=B2=B4,=20=EC=9D=B4=EB=AF=B8=EC=A7=80=203?= =?UTF-8?q?=EA=B0=9C=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=ED=94=84=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20dnd=20=EC=A0=81=EC=9A=A9=20=EB=93=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 135 +++---- package.json | 2 +- src/app/(pages)/(workform)/addform/page.tsx | 1 + .../addform/section/RecruitContentSection.tsx | 2 +- .../(pages)/work-talk/[talkId]/edit/page.tsx | 340 ++++++++++-------- .../[talkId]/sections/PostDetailSection.tsx | 153 ++++++-- src/app/(pages)/work-talk/add/page.tsx | 317 +++++++++------- .../input/file/ImageInput/ImageInput.tsx | 190 ++++++---- src/hooks/queries/post/useLikePost.ts | 10 - 9 files changed, 678 insertions(+), 472 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00307eff..252c7083 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@channel.io/channel-web-sdk-loader": "^2.0.0", + "@hello-pangea/dnd": "^17.0.0", "@hookform/resolvers": "^3.9.0", "@lottiefiles/react-lottie-player": "^3.5.4", "@next/third-parties": "^15.1.2", @@ -24,7 +25,6 @@ "next-sitemap": "^4.2.3", "prettier": "^3.3.3", "react": "^18", - "react-beautiful-dnd": "^13.1.1", "react-datepicker": "^7.5.0", "react-dom": "^18", "react-hook-form": "^7.53.0", @@ -2574,6 +2574,25 @@ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", "license": "MIT" }, + "node_modules/@hello-pangea/dnd": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz", + "integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.25.6", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^9.1.2", + "redux": "^5.0.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/@hookform/resolvers": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.1.tgz", @@ -7029,16 +7048,6 @@ "@types/send": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", - "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", - "license": "MIT", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -7107,6 +7116,7 @@ "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -7127,6 +7137,7 @@ "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -7176,18 +7187,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/react-redux": { - "version": "7.1.34", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", - "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", - "license": "MIT", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, "node_modules/@types/resolve": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", @@ -7225,6 +7224,12 @@ "@types/send": "*" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -11911,21 +11916,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/html-entities": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", @@ -13143,9 +13133,9 @@ } }, "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, "node_modules/memoizerific": { @@ -14882,26 +14872,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-beautiful-dnd": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", - "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", - "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.9.2", - "css-box-model": "^1.2.0", - "memoize-one": "^5.1.1", - "raf-schd": "^4.0.2", - "react-redux": "^7.2.0", - "redux": "^4.0.4", - "use-memo-one": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.5 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-confetti": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.1.0.tgz", @@ -15049,6 +15019,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, "license": "MIT" }, "node_modules/react-kakao-maps-sdk": { @@ -15066,26 +15037,24 @@ } }, "node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" }, "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" }, "peerDependenciesMeta": { - "react-dom": { + "@types/react": { "optional": true }, - "react-native": { + "redux": { "optional": true } } @@ -15298,13 +15267,10 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" }, "node_modules/reflect.getprototypeof": { "version": "1.0.9", @@ -17483,6 +17449,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", diff --git a/package.json b/package.json index 9e2f8f93..89bb857d 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@channel.io/channel-web-sdk-loader": "^2.0.0", + "@hello-pangea/dnd": "^17.0.0", "@hookform/resolvers": "^3.9.0", "@lottiefiles/react-lottie-player": "^3.5.4", "@next/third-parties": "^15.1.2", @@ -30,7 +31,6 @@ "next-sitemap": "^4.2.3", "prettier": "^3.3.3", "react": "^18", - "react-beautiful-dnd": "^13.1.1", "react-datepicker": "^7.5.0", "react-dom": "^18", "react-hook-form": "^7.53.0", diff --git a/src/app/(pages)/(workform)/addform/page.tsx b/src/app/(pages)/(workform)/addform/page.tsx index 5ebb55e5..ebba23b4 100644 --- a/src/app/(pages)/(workform)/addform/page.tsx +++ b/src/app/(pages)/(workform)/addform/page.tsx @@ -1,4 +1,5 @@ "use client"; + import { useEffect, useMemo, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { FormProvider, useForm } from "react-hook-form"; diff --git a/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx b/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx index 1c6f360f..402bc344 100644 --- a/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx +++ b/src/app/(pages)/(workform)/addform/section/RecruitContentSection.tsx @@ -1,4 +1,5 @@ "use client"; + import BaseInput from "@/app/components/input/text/BaseInput"; import BaseTextArea from "@/app/components/input/textarea/BaseTextArea"; import DatePickerInput from "@/app/components/input/dateTimeDaypicker/DatePickerInput"; @@ -10,7 +11,6 @@ import { ImageInputType } from "@/types/addform"; import useUploadImages from "@/hooks/queries/user/me/useImageUpload"; import { formatToLocaleDate } from "@/utils/formatters"; import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner"; -import { isDirty } from "zod"; import ImageInput from "@/app/components/input/file/ImageInput/ImageInput"; // 워크폼 만들기 - 사장님 - 1-모집내용 diff --git a/src/app/(pages)/work-talk/[talkId]/edit/page.tsx b/src/app/(pages)/work-talk/[talkId]/edit/page.tsx index 9d699585..704cbce4 100644 --- a/src/app/(pages)/work-talk/[talkId]/edit/page.tsx +++ b/src/app/(pages)/work-talk/[talkId]/edit/page.tsx @@ -1,11 +1,10 @@ "use client"; import React, { useState, useCallback, useEffect } from "react"; -import { useForm, SubmitHandler, Controller } from "react-hook-form"; +import { useForm, SubmitHandler, Controller, FormProvider } from "react-hook-form"; import { useRouter } from "next/navigation"; import Button from "@/app/components/button/default/Button"; import BaseInput from "@/app/components/input/text/BaseInput"; -import ImageInputPlaceHolder from "@/app/components/input/file/ImageInput/ImageInputPlaceHolder"; import { useEditPost } from "@/hooks/queries/post/useEditPost"; import { usePostDetail } from "@/hooks/queries/post/usePostDetail"; import axios from "axios"; @@ -14,29 +13,11 @@ import { PostSchema } from "@/schemas/postSchema"; import toast from "react-hot-toast"; import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner"; import { useQueryClient } from "@tanstack/react-query"; - -interface ImageInputType { - file: File | null; - url: string; - id: string; -} +import ImageInput from "@/app/components/input/file/ImageInput/ImageInput"; +import { ImageInputType } from "@/types/addform"; export default function EditTalk({ params }: { params: { talkId: string } }) { - const [imageList, setImageList] = useState([]); - const router = useRouter(); - const postId = params.talkId; - - const { data: post, isLoading, error } = usePostDetail(postId); - const { mutate: editPost, isPending } = useEditPost(postId); - const queryClient = useQueryClient(); - - const { - control, - handleSubmit, - setValue, - formState: { errors }, - reset, - } = useForm({ + const methods = useForm({ defaultValues: { title: "", content: "", @@ -44,6 +25,23 @@ export default function EditTalk({ params }: { params: { talkId: string } }) { }, }); + const { + control, + handleSubmit, + formState: { errors }, + reset, + setValue, + } = methods; + + const [isUploading, setIsUploading] = useState(false); + const [uploadedImages, setUploadedImages] = useState([]); + const router = useRouter(); + const postId = params.talkId; + + const { data: post, isLoading, error } = usePostDetail(postId); + const { mutate: editPost, isPending } = useEditPost(postId); + const queryClient = useQueryClient(); + // 게시글 데이터로 폼 초기화 useEffect(() => { if (post) { @@ -53,16 +51,13 @@ export default function EditTalk({ params }: { params: { talkId: string } }) { imageUrl: post.imageUrl || "", }); + // 이미지 초기화 if (post.imageUrl) { - setImageList([ - { - file: null, - url: post.imageUrl, - id: "initial-image", - }, - ]); - } else { - setImageList([]); + const initialImages = post.imageUrl.split(",").map((url) => ({ + url, + id: crypto.randomUUID(), + })); + setUploadedImages(initialImages); } } }, [post, reset]); @@ -81,48 +76,31 @@ export default function EditTalk({ params }: { params: { talkId: string } }) { try { const response = await axios.post("/api/images/upload", formData, { - headers: { - "Content-Type": "multipart/form-data", - }, + headers: { "Content-Type": "multipart/form-data" }, }); - - if (response.status === 201 && response.data?.url) { - return response.data.url; - } - - throw new Error("이미지 업로드에 실패했습니다."); + return response.data.url; } catch (error) { - toast.error("이미지 업로드에 실패했습니다."); console.error("이미지 업로드 실패:", error); throw error; } }, []); - const handleImagesChange = useCallback( - (newImages: ImageInputType[]) => { - setImageList(newImages); - const imageUrls = newImages.map((img) => img.url).join(","); - setValue("imageUrl", imageUrls); - }, - [setValue] - ); - const onSubmit: SubmitHandler = async (data) => { try { - if (!data.title) { - toast.error("제목을 입력하세요."); + if (!data.title || !data.content) { + toast.error("제목과 내용을 입력하세요."); return; } - if (!data.content) { - toast.error("내용을 입력하세요."); + if (isUploading) { + toast.error("이미지 업로드가 완료될 때까지 기다려주세요."); return; } const postData: PostSchema = { title: data.title, content: data.content, - imageUrl: imageList.map((img) => img.url).join(","), + imageUrl: data.imageUrl, }; editPost(postData, { @@ -154,108 +132,160 @@ export default function EditTalk({ params }: { params: { talkId: string } }) { if (!post) return null; return ( -
-
-
-
- 게시글 수정하기 -
-
- - -
-
-
-
- - ( - - )} - /> + +
+
+
+
게시글 수정
+
+ + +
+ +
+ + ( + + )} + /> +
-
- - ( -