Skip to content
94 changes: 39 additions & 55 deletions src/app/(pages)/(albaform)/addform/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import TabMenuDropdown from "@/app/components/button/dropdown/TabMenuDropdown";
import Button from "@/app/components/button/default/Button";
import { toast } from "react-hot-toast";
import { useMutation } from "@tanstack/react-query";
import { useUpdateProfile } from "@/hooks/queries/user/me/useUpdateProfile";
import RecruitContentSection from "./section/RecruitContentSection";
import RecruitConditionSection from "./section/RecruitConditionSection";
import WorkConditionSection from "./section/WorkConditionSection";
import useEditing from "@/hooks/useEditing";
import { SubmitFormDataType } from "@/types/addform";
import CustomFormModal from "@/app/components/modal/modals/confirm/CustomFormModal";
import tempSave from "@/utils/tempSave";
import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner";
import useUploadImages from "@/hooks/queries/user/me/useImageUpload";

export default function AddFormPage() {
const router = useRouter();
Expand Down Expand Up @@ -58,6 +59,19 @@ export default function AddFormPage() {
// 이미지 업로드 api 처리를 위해 별도 변수에 할당
const imageFiles = currentValues.imageFiles;
const [, setSelectedOption] = useState<string>("");
const [showTempDataModal, setShowTempDataModal] = useState(false);

const { uploadImages } = useUploadImages();

// 각각의 탭 작성중 여부
const { isEditingRecruitContent, isEditingRecruitCondition, isEditingWorkCondition } = useEditing(currentValues);

// tab 선택 시 Url params 수정 & 하위 폼 데이터 임시저장
const searchParams = useSearchParams();
const currentParam = searchParams.get("tab");
const [prevOption, setPrevOption] = useState<string | null>(null);
const initialLoad = currentParam === null; // 초기 로딩 여부 확인
const [loading, setLoading] = useState(false);

// 폼 제출 리액트쿼리
const mutation = useMutation({
Expand All @@ -72,7 +86,11 @@ export default function AddFormPage() {
// 이미지 업로드 처리
let uploadedUrls: string[] = [];
try {
uploadedUrls = await uploadImages(Array.from(imageFiles));
if (currentValues.imageUrls.length !== currentValues.imageFiles.length) {
uploadedUrls = await uploadImages(Array.from(imageFiles));
} else {
uploadedUrls = currentValues.imageUrls;
}
if (!uploadedUrls.length) {
toast.error("이미지 업로드에 실패했습니다.");
throw new Error("이미지 업로드 실패");
Expand Down Expand Up @@ -114,11 +132,11 @@ export default function AddFormPage() {
}
setLoading(false);
toast.success("알바폼을 등록했습니다.");
// if (formId) router.push(`/alba/${formId}`);
},
onError: (error) => {
setLoading(false);
toast.error("에러가 발생했습니다.");
console.error(error);
onTempSave();
},
});
Expand All @@ -129,12 +147,24 @@ export default function AddFormPage() {
}
}, [formId]);

// tab 선택 시 Url params 수정 & 하위 폼 데이터 임시저장
const searchParams = useSearchParams();
const currentParam = searchParams.get("tab");
const [prevOption, setPrevOption] = useState<string | null>(null);
const initialLoad = currentParam === null; // 초기 로딩 여부 확인
const [loading, setLoading] = useState(false);
// 폼데이터 임시 저장 함수
const onTempSave = async () => {
// 이미지 처리 로직
if (currentValues.imageUrls.length !== currentValues.imageFiles.length) {
try {
const uploadedUrls = await uploadImages(Array.from(imageFiles));
if (uploadedUrls && uploadedUrls.length > 0) {
setValue("imageUrls", [...uploadedUrls]);
} else {
setValue("imageUrls", [...currentValues.imageUrls]);
}
} catch (error) {
console.error("임시저장 - 이미지 업로드 중 오류 발생:", error);
setValue("imageUrls", []);
}
}
tempSave("addformData", currentValues);
};

const handleOptionChange = async (option: string) => {
setSelectedOption(option);
Expand Down Expand Up @@ -178,52 +208,6 @@ export default function AddFormPage() {
return <RecruitContentSection key="recruitContent" />;
}
};
const { uploadImageMutation } = useUpdateProfile();

// 이미지 업로드 api
const uploadImages = async (files: File[]) => {
if (currentValues.imageUrls.length !== currentValues.imageFiles.length) {
const uploadedUrls: string[] = [];

// 전체 파일 배열을 순회하면서 업로드 로직 진행
for (const file of files) {
// 파일 크기 체크
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
toast.error(`5MB 이상의 파일은 업로드할 수 없습니다.`);
continue;
}
const formData = new FormData();
formData.append("image", file);
try {
const uploadResponse = await uploadImageMutation.mutateAsync(file);
if (uploadResponse?.url) {
uploadedUrls.push(uploadResponse.url);
}
} catch (uploadError) {
console.error(`파일 ${file.name} 업로드 실패:`, uploadError);
}
}
return uploadedUrls;
} else {
return currentValues.imageUrls;
}
};

// 폼데이터 임시 저장 함수
const onTempSave = async () => {
// 임시저장
if (typeof window !== "undefined") {
window.localStorage.setItem("tempAddFormData", JSON.stringify(currentValues));
}
toast.success("임시 저장되었습니다.");
console.log("임시저장 데이터", currentValues);
};

// 각각의 탭 작성중 여부
const { isEditingRecruitContent, isEditingRecruitCondition, isEditingWorkCondition } = useEditing(currentValues);

const [showTempDataModal, setShowTempDataModal] = useState(false);

// 임시저장 데이터 로드 함수
const loadTempData = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export default function WorkConditionSection() {
value={workStartTime}
{...register("workStartTime", { required: "근무 시작 시간을 선택해주세요" })}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setValue("workStartTime", e.target.value);
setValue("workStartTime", e.target.value, { shouldDirty: true });
}}
/>
<TimePickerInput
Expand Down
7 changes: 2 additions & 5 deletions src/app/(pages)/(albaform)/alba/[formId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import useEditing from "@/hooks/useEditing";
import useFormDetail from "@/hooks/queries/form/detail/useFormDetail";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";
import formatMoney from "@/utils/formatMoney";
import tempSave from "@/utils/tempSave";

export default function EditFormPage() {
const router = useRouter();
Expand Down Expand Up @@ -127,11 +128,7 @@ export default function EditFormPage() {
setValue("imageUrls", []);
}
}
// 임시저장
if (typeof window !== "undefined") {
window.localStorage.setItem("tempAddFormData", JSON.stringify(currentValues));
}
console.log("임시저장 데이터", currentValues);
tempSave("addformData", currentValues);
};

// 수정된 폼 제출 리액트쿼리
Expand Down
35 changes: 3 additions & 32 deletions src/app/(pages)/(albaform)/apply/[formId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import toast from "react-hot-toast";
import { useParams, useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import Label from "../../component/Label";
import uploadResume from "@/utils/uploadResume";
import tempSave from "@/utils/tempSave";
interface ApplyFormData {
name: string;
phoneNumber: string;
Expand Down Expand Up @@ -48,33 +50,6 @@ export default function Apply() {
const currentValues = getValues();
const { resume, ...submitData } = currentValues;

// 이력서 업로드 api -> id, name 반환
const uploadResume = async (file: FileList) => {
const uploadedFile: { resumeName: string; resumeId: number } = {
resumeName: "",
resumeId: 0,
};
const formData = new FormData();
formData.append("file", file[0]);
try {
const response = await axios.post(`/api/resume/upload`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
timeout: 5000, // 5초 타임아웃 설정
});
console.log("이력서 업로드", response.data);
return {
resumeName: response.data.resumeName,
resumeId: response.data.resumeId,
};
} catch (error) {
console.error("Error uploading resume:", error);
toast.error("이력서 업로드에 실패했습니다.");
}
return uploadedFile;
};

// 폼 제출 리액트쿼리
const mutation = useMutation({
mutationFn: async () => {
Expand Down Expand Up @@ -103,11 +78,7 @@ export default function Apply() {
const uploadedResume = await uploadResume(currentValues.resume);
setValue("resumeId", uploadedResume.resumeId);
setValue("resumeName", uploadedResume.resumeName);

window.localStorage.setItem("tempApplyData", JSON.stringify(currentValues));
toast.success("임시 저장되었습니다.");
console.log("임시저장 currentData", currentValues);
console.log("임시저장 submitData", submitData);
tempSave("applyData", currentValues);
} catch (error) {
console.error("Error uploading resume:", error);
toast.error("이력서 업로드에 실패했습니다.");
Expand Down
2 changes: 0 additions & 2 deletions src/app/api/(file)/images/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export async function POST(req: NextRequest) {

try {
const formData = await req.formData();

try {
const response = await apiClient.post("/images/upload", formData, {
headers: {
Expand All @@ -21,7 +20,6 @@ export async function POST(req: NextRequest) {
"Content-Type": "multipart/form-data",
},
});

if (response.status === 201 && response.data?.url) {
return NextResponse.json(response.data, { status: 201 });
}
Expand Down
2 changes: 0 additions & 2 deletions src/app/api/forms/[formId]/applications/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export async function POST(req: NextRequest, { params }: { params: { formId: str
"Content-Type": "application/json",
},
});
console.log("apply 라우터에서 response.data출력 ", response.data);
return NextResponse.json(response.data);
} else {
const response = await apiClient.post(`/forms/${params.formId}/applications`, body, {
Expand All @@ -26,7 +25,6 @@ export async function POST(req: NextRequest, { params }: { params: { formId: str
"Content-Type": "application/json",
},
});
console.log("apply 라우터에서 response.data출력 ", response.data);
return NextResponse.json(response.data);
}
} catch (error: unknown) {
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/queries/user/me/useImageUpload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useUpdateProfile } from "@/hooks/queries/user/me/useUpdateProfile";
import renameFile from "@/utils/renameFile";
import toast from "react-hot-toast";

const useUploadImages = () => {
const { uploadImageMutation } = useUpdateProfile();

const uploadImages = async (files: File[]) => {
const uploadedUrls: string[] = [];

for (const file of files) {
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
toast.error(`5MB 이상의 파일은 업로드할 수 없습니다.`);
continue;
}

try {
const uploadResponse = await uploadImageMutation.mutateAsync(renameFile(file));
if (uploadResponse?.url) {
uploadedUrls.push(uploadResponse.url);
}
} catch (uploadError) {
console.error(`파일 ${file.name} 업로드 실패:`, uploadError);
}
}

return uploadedUrls;
};

return { uploadImages };
};

export default useUploadImages;
12 changes: 12 additions & 0 deletions src/utils/renameFile.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 해결해주셨군요 ! 감사합니다 ~

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 파일 이름을 임의의 값으로 수정하는 함수(한글 파일명 인코딩 방지)

const renameFile = (file: File) => {
const now = new Date();
const timestamp = now.toISOString().replace(/[-:T]/g, "").slice(0, 14);
const fileExtension = file.name.split(".").pop(); // 확장자 추출
const newFileName = `${timestamp}.${fileExtension}`; // 새로운 이름 생성

const renamedFile = new File([file], newFileName, { type: file.type });
return renamedFile;
};
export default renameFile;
11 changes: 11 additions & 0 deletions src/utils/tempSave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import toast from "react-hot-toast";

// 폼데이터 임시 저장 함수
const tempSave = async (name: string, data: any) => {
if (typeof window !== "undefined") {
window.localStorage.setItem(name, JSON.stringify(data));
}
toast.success("임시 저장되었습니다.");
console.log("임시저장 데이터", data);
};
export default tempSave;
31 changes: 31 additions & 0 deletions src/utils/uploadResume.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import axios from "axios";
import toast from "react-hot-toast";

// 이력서 업로드 api -> id, name 반환
const uploadResume = async (file: FileList) => {
const uploadedFile: { resumeName: string; resumeId: number } = {
resumeName: "",
resumeId: 0,
};
const formData = new FormData();
formData.append("file", file[0]);
try {
const response = await axios.post(`/api/resume/upload`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
timeout: 5000, // 5초 타임아웃 설정
});
console.log("이력서 업로드", response.data);
return {
resumeName: response.data.resumeName,
resumeId: response.data.resumeId,
};
} catch (error) {
console.error("Error uploading resume:", error);
toast.error("이력서 업로드에 실패했습니다.");
}
return uploadedFile;
};

export default uploadResume;
Loading