From b0254d76b19356dcd65a3f39bdecd7113b5168ee Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Wed, 24 Jul 2024 19:24:53 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20Toast=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feat: Toast 추가 --- app/addproject/AddProject/AddProjectContainer.tsx | 3 +++ .../[projectId]/edit/EditProject/EditProjectContainer.tsx | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/app/addproject/AddProject/AddProjectContainer.tsx b/app/addproject/AddProject/AddProjectContainer.tsx index 754b5609..9fef7775 100644 --- a/app/addproject/AddProject/AddProjectContainer.tsx +++ b/app/addproject/AddProject/AddProjectContainer.tsx @@ -10,6 +10,7 @@ import { getToken } from "@/app/_utils/handleToken"; import Input from "@/app/_components/Input/Input"; import { AddProjectFormData, ProjectLinkListType, TeammateType } from "@/app/_types/AddProjectFormDataType"; import useModal from "@/app/_hooks/useModal"; +import { useToast } from "@/app/_context/ToastContext"; import AddSection from "../_components/AddSection/AddSection"; import SkillStackSection from "../_components/SkillStack/SkillStackSection"; import ThumbnailBox from "../_components/ThumbnailBox"; @@ -36,6 +37,7 @@ function AddProjectContainer() { const queryClient = useQueryClient(); const router = useRouter(); const accessToken = getToken()?.accessToken; + const { addToast } = useToast(); const [formValues, setFormValues] = useState({ imageType: "웹", @@ -128,6 +130,7 @@ function AddProjectContainer() { }); console.log("Add Project Successful"); router.push("/main"); + addToast("프로젝트가 생성되었습니다", "success"); }, onError: () => { console.error("Add Project failed"); diff --git a/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx b/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx index 9a96d7e2..879be3f5 100644 --- a/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx +++ b/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx @@ -21,6 +21,7 @@ import { editProjectApi } from "@/app/_apis/editProjectApi"; import CancelModal from "@/app/addproject/_components/CancelModal/CancelModal"; import useModal from "@/app/_hooks/useModal"; import { getToken } from "@/app/_utils/handleToken"; +import { useToast } from "@/app/_context/ToastContext"; const TITLE_MAX_LENGTH = 50; const DESCRIPTION_MAX_LENGTH = 150; @@ -49,6 +50,8 @@ function EditProjectContainer({ projectId }: { projectId: number }) { const [imageType, setImageType] = useState("웹"); + const { addToast } = useToast(); + const { openModal: isCancelModalOpen, handleModalOpen: openCancelModal, @@ -166,6 +169,7 @@ function EditProjectContainer({ projectId }: { projectId: number }) { }); console.log("Edit Project Successful"); router.push(`/project/${projectId}`); + addToast("프로젝트가 수정되었습니다", "success"); }, onError: () => { console.error("Edit Project failed"); From 4fa12d2f5d47653cf57962918cd49d9cb509b1b5 Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Sun, 4 Aug 2024 23:21:05 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=EA=B8=B0=EC=88=A0=20?= =?UTF-8?q?=EC=8A=A4=ED=83=9D=20=EC=82=AD=EC=A0=9C=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8D=98=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix: 기술 스택 삭제 안되던 에러 수정 --- app/addproject/_components/SkillStack/SkillStackSection.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/addproject/_components/SkillStack/SkillStackSection.tsx b/app/addproject/_components/SkillStack/SkillStackSection.tsx index a45e43df..ba8ecc52 100644 --- a/app/addproject/_components/SkillStack/SkillStackSection.tsx +++ b/app/addproject/_components/SkillStack/SkillStackSection.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { useGetSkillStack } from "../../_context/SkillStackProvider"; import Title from "../Title"; import SkillStackSearch from "./SkillStackSearch"; @@ -13,12 +13,14 @@ interface SkillStackSectionProps { function SkillStackSection({ handleTechStackInput, initialStackList }: SkillStackSectionProps) { const { selectedStacks, isAddStack } = useGetSkillStack(); + const hasInitialized = useRef(false); useEffect(() => { - if (initialStackList && initialStackList.length > 0) { + if (!hasInitialized.current && initialStackList && initialStackList.length > 0) { initialStackList.forEach(stack => { isAddStack(stack); }); + hasInitialized.current = true; } }, [initialStackList, isAddStack]); From 45fcfd0f2aa7d7661a0fda4d118f233e439920ea Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Mon, 5 Aug 2024 00:36:44 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=ED=8C=80=EC=9B=90,?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=EB=A7=81=ED=81=AC=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=EC=97=90=20=EA=B0=92=EC=9D=B4=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=EA=B2=80=EC=82=AC=20=EB=B0=8F=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=EC=97=86=EB=8A=94=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix: 팀원, 추가링크 필요 항목에 값이 있는지 검사 및 필요없는 공백 제거 --- app/addproject/AddProject/AddProjectContainer.tsx | 6 ++++-- app/addproject/_components/AddSection/AddSection.tsx | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/addproject/AddProject/AddProjectContainer.tsx b/app/addproject/AddProject/AddProjectContainer.tsx index 9fef7775..51b79ae0 100644 --- a/app/addproject/AddProject/AddProjectContainer.tsx +++ b/app/addproject/AddProject/AddProjectContainer.tsx @@ -140,6 +140,8 @@ function AddProjectContainer() { const handleFormSubmit = async (data: AddProjectFormData) => { const formData = new FormData(); const thumbnailData = getValues("thumbnail"); + const teammateData = data.teammateList.filter(item => item.name.trim() !== "" && item.job.trim() !== ""); + const projectLinkData = data.projectLinkList.filter(item => item.siteType.trim() !== "" && item.url.trim() !== ""); const projectRequestDto = { title: data.title, @@ -148,8 +150,8 @@ function AddProjectContainer() { serviceUrl: data.serviceUrl, imageType: formValues.imageType === "웹" ? "WEB" : "MOBILE", projectTechStacks: data.projectTechStackList, - projectTeammates: data.teammateList, - projectLinks: data.projectLinkList, + projectTeammates: teammateData, + projectLinks: projectLinkData, }; formData.append("projectRequestDto", new Blob([JSON.stringify(projectRequestDto)], { type: "application/json" })); diff --git a/app/addproject/_components/AddSection/AddSection.tsx b/app/addproject/_components/AddSection/AddSection.tsx index 7b3b762b..862a21ff 100644 --- a/app/addproject/_components/AddSection/AddSection.tsx +++ b/app/addproject/_components/AddSection/AddSection.tsx @@ -100,7 +100,9 @@ function AddSection({ }; const handleInputChange = (id: number, field: string, value: string) => { - setAdditionalInput(prevInput => prevInput.map(input => (input.id === id ? { ...input, [field]: value } : input))); + setAdditionalInput(prevInput => + prevInput.map(input => (input.id === id ? { ...input, [field]: value.trim() } : input)) + ); }; useEffect(() => { From 1268cafb9804dfe662eece986cba8a6ab62fe0e4 Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Mon, 5 Aug 2024 20:39:29 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=ED=83=9C=EA=B7=B8=EC=97=90=20priority=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor: 이미지 태그에 priority 추가 --- .../_components/ProjectImageBox/ProjectImageCard.tsx | 9 ++++++++- app/addproject/_components/ThumbnailBox.tsx | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/addproject/_components/ProjectImageBox/ProjectImageCard.tsx b/app/addproject/_components/ProjectImageBox/ProjectImageCard.tsx index 8defd9c5..a445a566 100644 --- a/app/addproject/_components/ProjectImageBox/ProjectImageCard.tsx +++ b/app/addproject/_components/ProjectImageBox/ProjectImageCard.tsx @@ -11,7 +11,14 @@ interface ProjectImageCardProps { function ProjectImageCard({ index, imageUrl, handleImageDelete }: ProjectImageCardProps) { return (
- {`이미지 + {`이미지
handleImageDelete(index)}> diff --git a/app/addproject/_components/ThumbnailBox.tsx b/app/addproject/_components/ThumbnailBox.tsx index 815c899a..2874756e 100644 --- a/app/addproject/_components/ThumbnailBox.tsx +++ b/app/addproject/_components/ThumbnailBox.tsx @@ -46,6 +46,7 @@ function ThumbnailBox({ setThumbnail, register, initialUrl }: ThumbnailBoxProps) className="rounded-xl object-contain" src={showImageUrl} alt="썸네일 이미지" + priority />
From 3f748f7af6956f0d0e8659b87676cfa101b98612 Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Tue, 6 Aug 2024 14:04:08 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=ED=8C=80?= =?UTF-8?q?=EC=9B=90=20=EB=B0=8F=20=EC=B6=94=EA=B0=80=20=EB=A7=81=ED=81=AC?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B2=84=ED=8A=BC=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor: 팀원 및 추가 링크에서 버튼으로 데이터 삭제 가능하도록 수정 --- .../_components/AddSection/AddSection.tsx | 65 ++++++++++--------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/app/addproject/_components/AddSection/AddSection.tsx b/app/addproject/_components/AddSection/AddSection.tsx index 862a21ff..359b02f3 100644 --- a/app/addproject/_components/AddSection/AddSection.tsx +++ b/app/addproject/_components/AddSection/AddSection.tsx @@ -59,6 +59,32 @@ function AddSection({ clearErrors, }: AddSectionProps) { const [additionalInput, setAdditionalInput] = useState([]); + const [nextId, setNextId] = useState(1); + + const inputBoxLength = additionalInput.length > 1; + const hasInputData = + (title === "팀원" && !additionalInput[0]?.job && !additionalInput[0]?.name && !additionalInput[0]?.url) || + (title === "추가 링크" && !additionalInput[0]?.siteType && !additionalInput[0]?.url); + + const handleAddButtonClick = () => { + setAdditionalInput([...additionalInput, { id: nextId, url: "", job: "", name: "", siteType: "" }]); + setNextId(nextId + 1); + }; + + const handleDeleteButtonClick = (id: number) => { + if (inputBoxLength) { + setAdditionalInput(additionalInput.filter(item => item.id !== id)); + } else { + setAdditionalInput([{ id: nextId, url: "", job: "", name: "", siteType: "" }]); + setNextId(nextId + 1); + } + }; + + const handleInputChange = (id: number, field: string, value: string) => { + setAdditionalInput(prevInput => + prevInput.map(input => (input.id === id ? { ...input, [field]: value.trim() } : input)) + ); + }; useEffect(() => { if (initialTeammateList && initialTeammateList.length > 0) { @@ -86,37 +112,14 @@ function AddSection({ } }, [initialTeammateList, initialProjectLink]); - const [nextId, setNextId] = useState(1); - - const handleAddButtonClick = () => { - setAdditionalInput([...additionalInput, { id: nextId, url: "", job: "", name: "", siteType: "" }]); - setNextId(nextId + 1); - }; - - const handleDeleteButtonClick = (id: number) => { - if (additionalInput.length > 1) { - setAdditionalInput(additionalInput.filter(item => item.id !== id)); - } - }; - - const handleInputChange = (id: number, field: string, value: string) => { - setAdditionalInput(prevInput => - prevInput.map(input => (input.id === id ? { ...input, [field]: value.trim() } : input)) - ); - }; - useEffect(() => { + const hasError = title === "팀원" && additionalInput.every(input => !input.name || !input.job); onInputChange(additionalInput); - if (title === "팀원") { - let hasError = false; - if (!additionalInput[0]?.name || !additionalInput[0]?.job) { - setError && setError("teammateList", { type: "manual", message: "최소 한 개 이상의 팀원 정보를 추가해주세요" }); - hasError = true; - } - if (!hasError) { - clearErrors && clearErrors("teammateList"); - } + if (hasError) { + setError && setError("teammateList", { type: "manual", message: "최소 한 개 이상의 팀원 정보를 추가해주세요" }); + } else { + clearErrors && clearErrors("teammateList"); } }, [additionalInput, onInputChange, title, setError, clearErrors]); @@ -154,10 +157,12 @@ function AddSection({ )}
handleDeleteButtonClick(item.id)}>
1 ? "border-blue-500" : "border-gray-400"}`}> + className={`flex h-11 w-11 cursor-pointer items-center justify-center rounded-lg border border-solid ${ + inputBoxLength ? "border-blue-500" : hasInputData ? "border-gray-400" : "border-blue-500" + }`}> 1 ? blueDeleteIcon : grayDeleteIcon} + src={inputBoxLength ? blueDeleteIcon : hasInputData ? grayDeleteIcon : blueDeleteIcon} alt="삭제 버튼" priority /> From e685ca67d37a4a415ca43e27e6d90fb8b480f52b Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Tue, 6 Aug 2024 19:08:09 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=EB=93=9C?= =?UTF-8?q?=EB=A1=AD=EB=8B=A4=EC=9A=B4=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=3D>=20=EB=B0=95=EC=8A=A4=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EC=97=B4=EB=A6=AC=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor: 드롭다운 아이콘 클릭 => 박스 클릭 시 열리도록 수정 --- app/addproject/_components/DropDown/DropDownBox.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/addproject/_components/DropDown/DropDownBox.tsx b/app/addproject/_components/DropDown/DropDownBox.tsx index a1834659..76c820a8 100644 --- a/app/addproject/_components/DropDown/DropDownBox.tsx +++ b/app/addproject/_components/DropDown/DropDownBox.tsx @@ -60,9 +60,11 @@ function DropDownBox({ dataType, inputWidth, handleInputChange, initialDropDownV return (
+ className={`flex h-11 ${inputWidth ? inputWidth : "w-[118px]"} cursor-pointer items-center justify-between gap-2 rounded-lg border border-solid border-gray-200 p-2 text-sm font-normal text-gray-900`} + onClick={toggleState} + ref={exceptionRef}> {item} -
+
{isOpen ? ( 드롭다운 열기 ) : ( From 7d49967ee3eb7199530961eef28838c48394b558 Mon Sep 17 00:00:00 2001 From: JeongSabibi Date: Tue, 6 Aug 2024 20:59:34 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=EA=B8=B0?= =?UTF-8?q?=EC=88=A0=EC=8A=A4=ED=83=9D,=20=ED=8C=80=EC=9B=90=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=9E=91=EB=8F=99=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor: 기술스택, 팀원 유효성 검사 작동 수정 --- .../AddProject/AddProjectContainer.tsx | 20 ++++++-- .../_components/AddSection/AddSection.tsx | 17 +++++-- app/addproject/_components/Input.tsx | 3 +- .../SkillStack/SkillStackSearch.tsx | 49 ++++++++++++------- .../SkillStack/SkillStackSection.tsx | 5 +- 5 files changed, 65 insertions(+), 29 deletions(-) diff --git a/app/addproject/AddProject/AddProjectContainer.tsx b/app/addproject/AddProject/AddProjectContainer.tsx index 51b79ae0..40c99246 100644 --- a/app/addproject/AddProject/AddProjectContainer.tsx +++ b/app/addproject/AddProject/AddProjectContainer.tsx @@ -43,6 +43,9 @@ function AddProjectContainer() { imageType: "웹", imageList: [] as any[], }); + const [touchedStack, setTouchedStack] = useState(false); + const [touchedTeammate, setTouchedTeammate] = useState(false); + const imageIndexList = formValues.imageList.map((_, index) => index + 1); const { @@ -69,14 +72,14 @@ function AddProjectContainer() { (stackList: string[]) => { setValue("projectTechStackList", stackList); if (stackList.length > 0) clearErrors("projectTechStackList"); - else if (setError && stackList.length === 0) { + else if (setError && touchedStack && stackList.length === 0) { setError("projectTechStackList", { type: "manual", message: "최소 한 개 이상의 기술 스택을 추가해주세요", }); } }, - [clearErrors, setValue, setError] + [setValue, clearErrors, setError, touchedStack] ); const handleTeammateChange = useCallback( @@ -136,8 +139,15 @@ function AddProjectContainer() { console.error("Add Project failed"); }, }); - const handleFormSubmit = async (data: AddProjectFormData) => { + setTouchedStack(true); + setTouchedTeammate(true); + + const hasError = + data.teammateList.every(input => !input.name || !input.job) || data.projectTechStackList.length === 0; + + if (hasError) return; + const formData = new FormData(); const thumbnailData = getValues("thumbnail"); const teammateData = data.teammateList.filter(item => item.name.trim() !== "" && item.job.trim() !== ""); @@ -267,7 +277,7 @@ function AddProjectContainer() {
- + {errors.projectTechStackList && }
@@ -281,6 +291,8 @@ function AddProjectContainer() { inputWidth="w-[114px]" dropDownType="job" onInputChange={handleTeammateChange} + touchedTeammate={touchedTeammate} + setTouchedTeammate={setTouchedTeammate} /> {errors.teammateList && } diff --git a/app/addproject/_components/AddSection/AddSection.tsx b/app/addproject/_components/AddSection/AddSection.tsx index 359b02f3..a9b3f2a6 100644 --- a/app/addproject/_components/AddSection/AddSection.tsx +++ b/app/addproject/_components/AddSection/AddSection.tsx @@ -36,6 +36,8 @@ interface AddSectionProps extends InputHTMLAttributes { initialProjectLink?: any[]; setError?: UseFormSetError; clearErrors?: UseFormClearErrors; + touchedTeammate?: boolean; + setTouchedTeammate?: (isTouch: boolean) => void; } interface InputBox { @@ -57,6 +59,8 @@ function AddSection({ initialProjectLink, setError, clearErrors, + touchedTeammate, + setTouchedTeammate, }: AddSectionProps) { const [additionalInput, setAdditionalInput] = useState([]); const [nextId, setNextId] = useState(1); @@ -76,6 +80,7 @@ function AddSection({ setAdditionalInput(additionalInput.filter(item => item.id !== id)); } else { setAdditionalInput([{ id: nextId, url: "", job: "", name: "", siteType: "" }]); + setTouchedTeammate && setTouchedTeammate(true); setNextId(nextId + 1); } }; @@ -113,7 +118,7 @@ function AddSection({ }, [initialTeammateList, initialProjectLink]); useEffect(() => { - const hasError = title === "팀원" && additionalInput.every(input => !input.name || !input.job); + const hasError = touchedTeammate && title === "팀원" && additionalInput.every(input => !input.name || !input.job); onInputChange(additionalInput); if (hasError) { @@ -121,7 +126,7 @@ function AddSection({ } else { clearErrors && clearErrors("teammateList"); } - }, [additionalInput, onInputChange, title, setError, clearErrors]); + }, [additionalInput, onInputChange, title, setError, clearErrors, touchedTeammate]); return ( <> @@ -131,9 +136,10 @@ function AddSection({
- handleInputChange(item.id, title === "팀원" ? "job" : "siteType", value) - } + handleInputChange={(value: string) => { + setTouchedTeammate && setTouchedTeammate(true); + handleInputChange(item.id, title === "팀원" ? "job" : "siteType", value); + }} initialDropDownValue={title === "팀원" ? item.job : item.siteType} /> handleInputChange(item.id, title === "팀원" ? "name" : "url", event.target.value)} + onBlur={() => setTouchedTeammate && setTouchedTeammate(true)} /> {title === "팀원" && ( { inputWidth?: string; } -function Input({ type, placeholder, name, id, value, inputWidth, onChange }: InputSectionProps) { +function Input({ type, placeholder, name, id, value, inputWidth, onChange, onBlur }: InputSectionProps) { return ( ); } diff --git a/app/addproject/_components/SkillStack/SkillStackSearch.tsx b/app/addproject/_components/SkillStack/SkillStackSearch.tsx index 2ed383db..53a73941 100644 --- a/app/addproject/_components/SkillStack/SkillStackSearch.tsx +++ b/app/addproject/_components/SkillStack/SkillStackSearch.tsx @@ -1,14 +1,18 @@ "use client"; import Image from "next/image"; -import React, { ChangeEventHandler, FocusEventHandler, MouseEventHandler, useRef, useState } from "react"; +import React, { ChangeEventHandler, FocusEventHandler, MouseEventHandler, useCallback, useRef, useState } from "react"; import crossLineIcon from "@/public/icons/crossLine.svg"; import searchIcon from "@/public/icons/search.svg"; import { FULL_STACK_DATA } from "@/app/_constants/StackData"; import useOutsideClick from "@/app/_hooks/useOutsideClick"; import { useGetSkillStack } from "../../_context/SkillStackProvider"; -function SkillStackSearch() { +interface SkillStackSearchProps { + setTouchedStack?: (isTouch: boolean) => void; +} + +function SkillStackSearch({ setTouchedStack }: SkillStackSearchProps) { const stackData = FULL_STACK_DATA; const [isHidden, setIsHidden] = useState(true); const [liOver, setLiOver] = useState(false); @@ -19,31 +23,42 @@ function SkillStackSearch() { const { isAddStack } = useGetSkillStack(); - const handleInputChange: ChangeEventHandler = event => { + const handleInputChange: ChangeEventHandler = useCallback(event => { const { value } = event.currentTarget; setSearch(value); - }; + }, []); - const handleInputFocusIn: FocusEventHandler = () => { + const handleInputFocusIn: FocusEventHandler = useCallback(() => { setIsHidden(false); - }; - const handleInputFocusOut: FocusEventHandler = () => { + }, []); + + const handleInputFocusOut: FocusEventHandler = useCallback(() => { if (liOver) return; setIsHidden(true); - }; + if (setTouchedStack) { + setTouchedStack(true); + } + }, [liOver, setTouchedStack]); - const handleMouseOver: MouseEventHandler = () => { + const handleMouseOver: MouseEventHandler = useCallback(() => { setLiOver(true); - }; - const handleMouseLeave: MouseEventHandler = () => { + }, []); + + const handleMouseLeave: MouseEventHandler = useCallback(() => { setLiOver(false); - }; + }, []); - const handleAddStack = (stack: string) => { - isAddStack(stack); - setSearch(""); - setIsHidden(true); - }; + const handleAddStack = useCallback( + (stack: string) => { + isAddStack(stack); + setSearch(""); + setIsHidden(true); + if (setTouchedStack) { + setTouchedStack(true); + } + }, + [isAddStack, setTouchedStack] + ); useOutsideClick(dropdownRef, () => setIsHidden(true), exceptionRef); diff --git a/app/addproject/_components/SkillStack/SkillStackSection.tsx b/app/addproject/_components/SkillStack/SkillStackSection.tsx index ba8ecc52..f8d9679c 100644 --- a/app/addproject/_components/SkillStack/SkillStackSection.tsx +++ b/app/addproject/_components/SkillStack/SkillStackSection.tsx @@ -9,9 +9,10 @@ import SelectSkillStack from "./SelectSkillStack"; interface SkillStackSectionProps { handleTechStackInput: (stackList: string[]) => void; initialStackList?: string[]; + setTouchedStack?: (isTouch: boolean) => void; } -function SkillStackSection({ handleTechStackInput, initialStackList }: SkillStackSectionProps) { +function SkillStackSection({ handleTechStackInput, initialStackList, setTouchedStack }: SkillStackSectionProps) { const { selectedStacks, isAddStack } = useGetSkillStack(); const hasInitialized = useRef(false); @@ -31,7 +32,7 @@ function SkillStackSection({ handleTechStackInput, initialStackList }: SkillStac return ( <> - <SkillStackSearch /> + <SkillStackSearch setTouchedStack={setTouchedStack} /> <SelectSkillStack /> </> ); From 74a02b749adcd7be16edc5bbc8ba1d64bdfdaeb4 Mon Sep 17 00:00:00 2001 From: JeongSabibi <jsbb528@naver.com> Date: Tue, 6 Aug 2024 21:05:16 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=88=98=EC=A0=95=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=9D=98=20=EA=B8=B0=EC=88=A0=EC=8A=A4?= =?UTF-8?q?=ED=83=9D,=20=ED=8C=80=EC=9B=90=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=20=EC=9E=91=EB=8F=99=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor: 프로젝트 수정 페이지의 기술스택, 팀원 유효성 검사 작동 수정 --- .../AddProject/AddProjectContainer.tsx | 1 + .../edit/EditProject/EditProjectContainer.tsx | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/addproject/AddProject/AddProjectContainer.tsx b/app/addproject/AddProject/AddProjectContainer.tsx index 40c99246..134d349e 100644 --- a/app/addproject/AddProject/AddProjectContainer.tsx +++ b/app/addproject/AddProject/AddProjectContainer.tsx @@ -139,6 +139,7 @@ function AddProjectContainer() { console.error("Add Project failed"); }, }); + const handleFormSubmit = async (data: AddProjectFormData) => { setTouchedStack(true); setTouchedTeammate(true); diff --git a/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx b/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx index 879be3f5..81bf521e 100644 --- a/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx +++ b/app/project/[projectId]/edit/EditProject/EditProjectContainer.tsx @@ -47,8 +47,9 @@ function EditProjectContainer({ projectId }: { projectId: number }) { const router = useRouter(); const [imageList, setImageList] = useState([] as any[]); - const [imageType, setImageType] = useState("웹"); + const [touchedStack, setTouchedStack] = useState(false); + const [touchedTeammate, setTouchedTeammate] = useState(false); const { addToast } = useToast(); @@ -108,14 +109,14 @@ function EditProjectContainer({ projectId }: { projectId: number }) { setValue("projectTechStackList", updatedStackList); if (stackList.length > 0) clearErrors("projectTechStackList"); - else if (setError && stackList.length === 0) { + else if (setError && touchedStack && stackList.length === 0) { setError("projectTechStackList", { type: "manual", message: "최소 한 개 이상의 기술 스택을 추가해주세요", }); } }, - [project?.techStacks, setValue, clearErrors, setError] + [project?.techStacks, setValue, clearErrors, setError, touchedStack] ); const handleTeammateChange = useCallback( @@ -177,6 +178,14 @@ function EditProjectContainer({ projectId }: { projectId: number }) { }); const handleFormSubmit = async (data: EditProjectFormData) => { + setTouchedStack(true); + setTouchedTeammate(true); + + const hasError = + data.teammateList.every(input => !input.name || !input.job) || data.projectTechStackList.length === 0; + + if (hasError) return; + const formData = new FormData(); const thumbnailData = getValues("thumbnail"); const teammateData = data.teammateList.filter(item => item.name.trim() !== "" && item.job.trim() !== ""); @@ -314,6 +323,7 @@ function EditProjectContainer({ projectId }: { projectId: number }) { <SkillStackSection handleTechStackInput={handleTechStackInput} initialStackList={project?.techStacks?.map(stack => stack.techStack)} + setTouchedStack={setTouchedStack} /> {errors.projectTechStackList && <ErrorMessage error={errors.projectTechStackList} />} </SkillStackProvider> @@ -329,6 +339,8 @@ function EditProjectContainer({ projectId }: { projectId: number }) { dropDownType="job" onInputChange={handleTeammateChange} initialTeammateList={project?.projectTeammates} + touchedTeammate={touchedTeammate} + setTouchedTeammate={setTouchedTeammate} /> {errors.teammateList && <ErrorMessage error={errors.teammateList} />} </section>