Skip to content

Commit

Permalink
🐛 Fix: 충돌 해결
Browse files Browse the repository at this point in the history
Fix: 충돌 해결
  • Loading branch information
JeongSabibi committed Aug 6, 2024
2 parents ce39291 + 74a02b7 commit 1ffec10
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 65 deletions.
28 changes: 23 additions & 5 deletions app/addproject/AddProject/AddProjectContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -36,11 +37,15 @@ function AddProjectContainer() {
const queryClient = useQueryClient();
const router = useRouter();
const accessToken = getToken()?.accessToken;
const { addToast } = useToast();

const [formValues, setFormValues] = useState({
imageType: "웹",
imageList: [] as any[],
});
const [touchedStack, setTouchedStack] = useState(false);
const [touchedTeammate, setTouchedTeammate] = useState(false);

const imageIndexList = formValues.imageList.map((_, index) => index + 1);

const {
Expand All @@ -67,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(
Expand Down Expand Up @@ -128,15 +133,26 @@ function AddProjectContainer() {
});
console.log("Add Project Successful");
router.push("/main");
addToast("프로젝트가 생성되었습니다", "success");
},
onError: () => {
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() !== "");
const projectLinkData = data.projectLinkList.filter(item => item.siteType.trim() !== "" && item.url.trim() !== "");

const projectRequestDto = {
title: data.title,
Expand All @@ -145,8 +161,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" }));
Expand Down Expand Up @@ -262,7 +278,7 @@ function AddProjectContainer() {
</section>
<section className="flex w-[690px] flex-col gap-4">
<SkillStackProvider>
<SkillStackSection handleTechStackInput={handleTechStackInput} />
<SkillStackSection handleTechStackInput={handleTechStackInput} setTouchedStack={setTouchedStack} />
{errors.projectTechStackList && <ErrorMessage error={errors.projectTechStackList} />}
</SkillStackProvider>
</section>
Expand All @@ -276,6 +292,8 @@ function AddProjectContainer() {
inputWidth="w-[114px]"
dropDownType="job"
onInputChange={handleTeammateChange}
touchedTeammate={touchedTeammate}
setTouchedTeammate={setTouchedTeammate}
/>
{errors.teammateList && <ErrorMessage error={errors.teammateList} />}
</section>
Expand Down
78 changes: 46 additions & 32 deletions app/addproject/_components/AddSection/AddSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ interface AddSectionProps extends InputHTMLAttributes<HTMLInputElement> {
initialProjectLink?: any[];
setError?: UseFormSetError<AddProjectFormData>;
clearErrors?: UseFormClearErrors<AddProjectFormData>;
touchedTeammate?: boolean;
setTouchedTeammate?: (isTouch: boolean) => void;
}

interface InputBox {
Expand All @@ -57,8 +59,37 @@ function AddSection({
initialProjectLink,
setError,
clearErrors,
touchedTeammate,
setTouchedTeammate,
}: AddSectionProps) {
const [additionalInput, setAdditionalInput] = useState<InputBox[]>([]);
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: "" }]);
setTouchedTeammate && setTouchedTeammate(true);
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) {
Expand Down Expand Up @@ -86,37 +117,16 @@ 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 } : input)));
};

useEffect(() => {
const hasError = touchedTeammate && 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]);
}, [additionalInput, onInputChange, title, setError, clearErrors, touchedTeammate]);

return (
<>
Expand All @@ -126,9 +136,10 @@ function AddSection({
<div key={item.id} className="mb-2 flex gap-1">
<DropDownBox
dataType={dropDownType}
handleInputChange={(value: string) =>
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}
/>
<Input
Expand All @@ -139,6 +150,7 @@ function AddSection({
value={title === "팀원" ? item.name : item.url}
inputWidth={inputWidth}
onChange={event => handleInputChange(item.id, title === "팀원" ? "name" : "url", event.target.value)}
onBlur={() => setTouchedTeammate && setTouchedTeammate(true)}
/>
{title === "팀원" && (
<Input
Expand All @@ -152,10 +164,12 @@ function AddSection({
)}
<div className="min-w-11" onClick={() => handleDeleteButtonClick(item.id)}>
<div
className={`flex h-11 w-11 cursor-pointer items-center justify-center rounded-lg border border-solid ${additionalInput.length > 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"
}`}>
<Image
width={17}
src={additionalInput.length > 1 ? blueDeleteIcon : grayDeleteIcon}
src={inputBoxLength ? blueDeleteIcon : hasInputData ? grayDeleteIcon : blueDeleteIcon}
alt="삭제 버튼"
priority
/>
Expand Down
6 changes: 4 additions & 2 deletions app/addproject/_components/DropDown/DropDownBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ function DropDownBox({ dataType, inputWidth, handleInputChange, initialDropDownV
return (
<div className="relative">
<div
className={`flex h-11 ${inputWidth ? inputWidth : "w-[118px]"} items-center justify-between gap-2 rounded-lg border border-solid border-gray-200 p-2 text-sm font-normal text-gray-900`}>
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}
<div className="h-5 w-5 cursor-pointer" onClick={toggleState} ref={exceptionRef}>
<div className="h-5 w-5 cursor-pointer">
{isOpen ? (
<Image src={smallTopArrowIcon} alt="드롭다운 열기" width={20} height={20} priority />
) : (
Expand Down
3 changes: 2 additions & 1 deletion app/addproject/_components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface InputSectionProps extends InputHTMLAttributes<HTMLInputElement> {
inputWidth?: string;
}

function Input({ type, placeholder, name, id, value, inputWidth, onChange }: InputSectionProps) {
function Input({ type, placeholder, name, id, value, inputWidth, onChange, onBlur }: InputSectionProps) {
return (
<input
type={type}
Expand All @@ -15,6 +15,7 @@ function Input({ type, placeholder, name, id, value, inputWidth, onChange }: Inp
value={value}
autoComplete="off"
onChange={onChange}
onBlur={onBlur}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ interface ProjectImageCardProps {
function ProjectImageCard({ index, imageUrl, handleImageDelete }: ProjectImageCardProps) {
return (
<div className="relative h-[220px] w-[220px] rounded-xl border border-solid border-gray-300 bg-white hover:cursor-move hover:border-blue-500 hover:shadow-md active:border-blue-500">
<Image fill sizes="max-width" className="rounded-xl object-contain" src={imageUrl} alt={`이미지 ${index + 1}`} />
<Image
fill
sizes="max-width"
className="rounded-xl object-contain"
src={imageUrl}
alt={`이미지 ${index + 1}`}
priority
/>
<div
className="absolute right-3 top-3 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-gray-700"
onClick={() => handleImageDelete(index)}>
Expand Down
49 changes: 32 additions & 17 deletions app/addproject/_components/SkillStack/SkillStackSearch.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -19,31 +23,42 @@ function SkillStackSearch() {

const { isAddStack } = useGetSkillStack();

const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
const handleInputChange: ChangeEventHandler<HTMLInputElement> = useCallback(event => {
const { value } = event.currentTarget;
setSearch(value);
};
}, []);

const handleInputFocusIn: FocusEventHandler<HTMLInputElement> = () => {
const handleInputFocusIn: FocusEventHandler<HTMLInputElement> = useCallback(() => {
setIsHidden(false);
};
const handleInputFocusOut: FocusEventHandler<HTMLInputElement> = () => {
}, []);

const handleInputFocusOut: FocusEventHandler<HTMLInputElement> = useCallback(() => {
if (liOver) return;
setIsHidden(true);
};
if (setTouchedStack) {
setTouchedStack(true);
}
}, [liOver, setTouchedStack]);

const handleMouseOver: MouseEventHandler<HTMLLIElement> = () => {
const handleMouseOver: MouseEventHandler<HTMLLIElement> = useCallback(() => {
setLiOver(true);
};
const handleMouseLeave: MouseEventHandler<HTMLLIElement> = () => {
}, []);

const handleMouseLeave: MouseEventHandler<HTMLLIElement> = 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);

Expand Down
11 changes: 7 additions & 4 deletions app/addproject/_components/SkillStack/SkillStackSection.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -9,16 +9,19 @@ 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);

useEffect(() => {
if (initialStackList && initialStackList.length > 0) {
if (!hasInitialized.current && initialStackList && initialStackList.length > 0) {
initialStackList.forEach(stack => {
isAddStack(stack);
});
hasInitialized.current = true;
}
}, [initialStackList, isAddStack]);

Expand All @@ -29,7 +32,7 @@ function SkillStackSection({ handleTechStackInput, initialStackList }: SkillStac
return (
<>
<Title title="기술스택" name="search" label />
<SkillStackSearch />
<SkillStackSearch setTouchedStack={setTouchedStack} />
<SelectSkillStack />
</>
);
Expand Down
1 change: 1 addition & 0 deletions app/addproject/_components/ThumbnailBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function ThumbnailBox({ setThumbnail, register, initialUrl }: ThumbnailBoxProps)
className="rounded-xl object-contain"
src={showImageUrl}
alt="썸네일 이미지"
priority
/>
<div className="absolute inset-0 bg-gray-200 opacity-0 transition-opacity duration-300 group-hover:opacity-50" />
<div className="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity duration-300 group-hover:opacity-100">
Expand Down
Loading

0 comments on commit 1ffec10

Please sign in to comment.