Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .env.example

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build || true",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write ."
Expand Down
84 changes: 79 additions & 5 deletions src/app/[type]/mypage/[userId]/resume/[resumeId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,49 @@ import { resumeApi } from "@/api/resume";
import type { ResumeResponseDto } from "@/types/api/resume";
import type { ResumeFormData } from "@/features/resume/validation/resumeSchema";

const ErrorCard = ({
title,
message,
onRetry,
showRetry = true,
actionButton,
}: {
title: string;
message: string;
onRetry?: () => void;
showRetry?: boolean;
actionButton?: React.ReactNode;
}) => (
<div className="flex justify-center items-center flex-1">
<div className="bg-white rounded-lg shadow-md px-10 py-[80px] w-full max-w-[1000px]">
<div className="flex flex-col items-center justify-center space-y-6">
<div className="bg-red-50 border border-red-200 rounded-lg p-8 max-w-md text-center">
<div className="text-red-600 text-5xl mb-4">⚠️</div>
<h2 className="text-xl font-semibold text-red-700 mb-3">{title}</h2>
<p className="text-red-600 mb-6">{message}</p>

<div className="flex flex-col gap-3">
{showRetry && onRetry && (
<button
onClick={onRetry}
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors"
>
다시 시도
</button>
)}
{actionButton}
</div>
</div>
</div>
</div>
</div>
);

export default function ResumeEditPage() {
const router = useRouter();
const { data: session, status: sessionStatus } = useSession();
const { userId, resumeId } = useParams() as {
const { type, userId, resumeId } = useParams() as {
type: string;
userId: string;
resumeId: string;
};
Expand All @@ -33,6 +72,7 @@ export default function ResumeEditPage() {
data: detailData,
isLoading: isDetailLoading,
error: detailError,
refetch: refetchDetail,
} = useQuery<ResumeResponseDto, Error>({
queryKey: ["resumeDetail", resumeId],
queryFn: () => resumeApi.getDetail(resumeId!, session?.accessToken ?? ""),
Expand All @@ -42,17 +82,48 @@ export default function ResumeEditPage() {
if (sessionStatus === "loading" || isDetailLoading) {
return <Spinner />;
}

if (detailError) {
return <p className="text-red-500">이력서 불러오기 실패: {detailError.message}</p>;
// 확인요망
const getErrorMessage = (error: Error) => {
if (error.message.includes("404")) {
return "존재하지 않는 이력서이거나 삭제된 이력서입니다.";
}
if (error.message.includes("403")) {
return "이 이력서를 수정할 권한이 없습니다.";
}
return "이력서를 불러오는 중 문제가 발생했습니다. 네트워크 연결을 확인해주세요.";
};

return (
<ErrorCard
title="이력서를 불러올 수 없습니다"
message={getErrorMessage(detailError)}
onRetry={refetchDetail}
actionButton={
<div className="flex flex-col gap-2">
<button
onClick={() => router.push(`/${type}/mypage/${userId}`)}
className="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 transition-colors"
>
마이페이지로 이동
</button>
<button
onClick={() => router.back()}
className="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>
이전 페이지로
</button>
</div>
}
/>
);
}

const dto = detailData!.resume;
const defaultValues: ResumeFormData = {
jobCategory: dto.job_category,
title: dto.resume_title,
name: dto.user.name,
phone: dto.user.phone_number,
email: session!.user.email ?? "",
schoolType: dto.education_level,
schoolName: dto.school_name,
graduationStatus: dto.education_state,
Expand All @@ -69,6 +140,9 @@ export default function ResumeEditPage() {
date: c.date_acquired,
})),
introduction: dto.introduce,
name: "",
phone: "",
email: "",
};

return (
Expand Down
2 changes: 1 addition & 1 deletion src/app/[type]/mypage/[userId]/resume/[resumeId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useSession } from "next-auth/react";
import Spinner from "@/components/common/Spinner";
import ResumeSelect from "@/features/resume/components/common/ui/ResumeSelect";
import ResumeContainer from "@/features/resume/components/ResumeContainer";
import ResumeActionButtons from "@/features/resume/components/ResumeActionButton";
import ResumeActionButtons from "@/features/resume/components/sections/ResumeActionButton";
import { useGetResumeDetail } from "@/features/resume/api/useGetResumeDetail";
import { useGetResumeList } from "@/features/resume/api/useGetResumeList";
import { mapToResumeFormData } from "@/features/resume/utils/mapToResumeFormData";
Expand Down
13 changes: 13 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,16 @@
.animate-slide-down {
animation: slide-up 0.3s ease-out;
}

/* Screen reader only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
96 changes: 52 additions & 44 deletions src/components/common/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,61 @@
"use client";
import { Fragment } from "react";
import { Transition, TransitionChild, Dialog, Description } from "@headlessui/react";
import { useModalStore } from "@/store/useModalStore";
import { useEffect } from "react";

export default function ConfirmModal() {
const { isOpen, title, message, confirmText, onConfirm, closeModal } = useModalStore();

return (
<Transition appear show={isOpen} as={Fragment}>
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-40"
leave="duration-0"
>
<div className="fixed inset-0 bg-black opacity-40" />
</TransitionChild>
const handleConfirm = () => {
onConfirm?.();
closeModal();
};

useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === "Escape" && isOpen) {
closeModal();
}
};

if (isOpen) {
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
document.body.style.overflow = "hidden";
document.body.style.paddingRight = `${scrollbarWidth}px`;
document.addEventListener("keydown", handleEsc);
}

<Dialog
as="div"
className="fixed inset-0 z-50 flex items-center justify-center p-4"
onClose={closeModal}
>
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-0"
>
<div className="bg-white rounded-lg shadow-lg p-6 w-full max-w-sm">
<h2 className="text-xl font-semibold mb-2 text-center text-primary">{title}</h2>
<Description className="text-base mb-7 mt-3 whitespace-pre-line text-center">
{message}
</Description>
<button
type="button"
className="w-full h-12 bg-primary text-white font-medium rounded-sm hover:opacity-90 transition"
onClick={() => {
onConfirm?.();
closeModal();
}}
>
{confirmText}
</button>
</div>
</TransitionChild>
</Dialog>
</Transition>
return () => {
document.body.style.overflow = "unset";
document.body.style.paddingRight = "0px";
document.removeEventListener("keydown", handleEsc);
};
}, [isOpen, closeModal]);

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="fixed inset-0 bg-black/40" onClick={closeModal} />
<div className="relative bg-white rounded-lg shadow-lg p-6 w-full max-w-sm">
<h2 className="text-xl font-semibold mb-2 text-center text-primary">{title}</h2>
<p className="text-base mb-7 mt-3 whitespace-pre-line text-center">{message}</p>
<div className="flex gap-3">
<button
type="button"
className="flex-1 h-12 bg-gray-200 text-gray-700 font-medium rounded-sm hover:bg-gray-300 transition-colors"
onClick={closeModal}
>
취소
</button>
<button
type="button"
className="flex-1 h-12 bg-primary text-white font-medium rounded-sm hover:opacity-90 transition-opacity"
onClick={handleConfirm}
>
{confirmText}
</button>
</div>
</div>
</div>
);
}
6 changes: 3 additions & 3 deletions src/features/applicants/components/ApplicantsResume.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client";

import { applicantListApi } from "@/api/applicant";
import ResumeContactSection from "@/features/resume/components/ResumeContactSection";
import ResumeSelfIntroductionSection from "@/features/resume/components/ResumeSelfIntroductionSection";
import ResumeTableSection from "@/features/resume/components/ResumeTableSection";
import ResumeContactSection from "@/features/resume/components/sections/ResumeContactSection";
import ResumeSelfIntroductionSection from "@/features/resume/components/sections/ResumeSelfIntroductionSection";
import ResumeTableSection from "@/features/resume/components/sections/ResumeTableSection";
import { useQuery } from "@tanstack/react-query";
import { useParams } from "next/navigation";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use client";

import { Controller, useFormContext, FieldValues, Path } from "react-hook-form";
import { CalendarIcon } from "lucide-react";
import DatePicker from "react-datepicker";
Expand All @@ -25,6 +24,13 @@ export default function FormDatePicker<T extends FieldValues>({
formState: { errors },
} = useFormContext<T>();

const formatDateToLocal = (date: Date): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};

return (
<div className="w-full">
<label className="block mb-3 ml-2 font-semibold text-base sm:text-lg">{label}</label>
Expand All @@ -36,7 +42,7 @@ export default function FormDatePicker<T extends FieldValues>({
<DatePicker
selected={field.value ? new Date(field.value) : null}
onChange={(date: Date | null) => {
const formatted = date?.toISOString().split("T")[0] || "";
const formatted = date ? formatDateToLocal(date) : "";
field.onChange(formatted);
}}
dateFormat="yyyy-MM-dd"
Expand Down
Loading
Loading