Skip to content
Merged
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
53 changes: 32 additions & 21 deletions src/app/(pages)/(albaform)/apply/[formId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { cn } from "@/lib/tailwindUtil";
import axios from "axios";
import toast from "react-hot-toast";
import { useParams, useRouter } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import Label from "../../component/Label";
import uploadResume from "@/utils/uploadResume";
import tempSave from "@/utils/tempSave";
Expand Down Expand Up @@ -47,44 +47,55 @@ export default function Apply() {

const formId = useParams().formId;
const router = useRouter();
const currentValues = getValues();
const { resume, ...submitData } = currentValues;
const queryClient = useQueryClient();

const onTempSave = async () => {
try {
const values = getValues();
const uploadedResume = await uploadResume(values.resume);
setValue("resumeId", uploadedResume.resumeId);
setValue("resumeName", uploadedResume.resumeName);
} catch (error) {
console.error("Error uploading resume:", error);
toast.error("이력서 업로드에 실패했습니다.");
}
};

// 폼 제출 리액트쿼리
const mutation = useMutation({
mutationFn: async () => {
console.log("apply 제출 submitData 출력", submitData);
// 이력서 업로드 및 임시저장 먼저 수행
await onTempSave();

// 최신 값을 가져오기
const values = getValues();
const { resume, ...submitData } = values;

const response = await axios.post(`/api/forms/${formId}/applications`, submitData);
console.log("apply 제출 response.data 출력", response.data);
return response.data;
},

onSuccess: () => {
// 로컬 스토리지 데이터 삭제
if (typeof window !== "undefined") {
window.localStorage.removeItem("tempAddFormData");
}
toast.success("알바폼을 등록했습니다.");
router.push(`/alba/${formId}`);

// 내 지원서 목록 캐시 무효화
queryClient.invalidateQueries({ queryKey: ["myApplications"] });

toast.success("지원이 완료되었습니다.");
router.push("/myalbaform");
},

onError: (error) => {
console.error("에러가 발생했습니다.", error);
toast.error("에러가 발생했습니다.");
console.error("지원하기에 실패했습니다.", error);
toast.error("지원하기에 실패했습니다.");
// 에러 발생 시 자동으로 임시저장
onTempSave();
},
});

const onTempSave = async () => {
try {
const uploadedResume = await uploadResume(currentValues.resume);
setValue("resumeId", uploadedResume.resumeId);
setValue("resumeName", uploadedResume.resumeName);
tempSave("applyData", currentValues);
} catch (error) {
console.error("Error uploading resume:", error);
toast.error("이력서 업로드에 실패했습니다.");
}
};

const errorTextStyle =
"absolute -bottom-[26px] right-1 text-[13px] text-sm font-medium leading-[22px] text-state-error lg:text-base lg:leading-[26px]";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function PostDetailSection({ postId }: { postId: string }) {
onConfirm: () => {},
onCancel: () => {},
});
router.push("/albatalk");
},
});
},
Expand Down Expand Up @@ -82,12 +83,48 @@ export function PostDetailSection({ postId }: { postId: string }) {
</div>

{/* Author Info */}
<div className="mb-4 flex items-center gap-2">
<Image src="/icons/user/user-profile-sm.svg" alt="User Icon" className="rounded-full" width={32} height={32} />
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium text-black-400">{post?.writer.nickname}</span>
<span className="text-grayscale-400">|</span>
<span className="text-sm text-grayscale-400">{formatLocalDate(post?.createdAt || new Date())}</span>
<Image
src="/icons/user/user-profile-sm.svg"
alt="User Icon"
className="rounded-full"
width={32}
height={32}
/>
<div className="flex items-center gap-2">
<span className="font-medium text-black-400">{post?.writer.nickname}</span>
<span className="text-grayscale-400">|</span>
<span className="text-sm text-grayscale-400">{formatLocalDate(post?.createdAt || new Date())}</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Image
src={`/icons/comment/${isDesktop ? "comment-md.svg" : "comment-sm.svg"}`}
alt="Comment Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.commentCount}</span>
</div>
<div className="flex cursor-pointer items-center gap-1" onClick={handleLikeClick}>
<Image
src={`/icons/like/${
post?.isLiked
? isDesktop
? "like-md-active.svg"
: "like-sm-active.svg"
: isDesktop
? "like-md.svg"
: "like-sm.svg"
}`}
alt="Like Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.likeCount}</span>
</div>
</div>
</div>

Expand All @@ -111,36 +148,6 @@ export function PostDetailSection({ postId }: { postId: string }) {
</div>
) : null}
</div>

{/* Footer */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Image
src={`/icons/comment/${isDesktop ? "comment-md.svg" : "comment-sm.svg"}`}
alt="Comment Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.commentCount}</span>
</div>
<div className="flex cursor-pointer items-center gap-1" onClick={handleLikeClick}>
<Image
src={`/icons/like/${
post?.isLiked
? isDesktop
? "like-md-active.svg"
: "like-sm-active.svg"
: isDesktop
? "like-md.svg"
: "like-sm.svg"
}`}
alt="Like Icon"
width={24}
height={24}
/>
<span className="text-sm text-grayscale-500">{post?.likeCount}</span>
</div>
</div>
</section>
);
}
19 changes: 10 additions & 9 deletions src/app/api/posts/[postId]/like/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import apiClient from "@/lib/apiClient";
// 게시글 좋아요 API
export async function POST(request: Request, { params }: { params: { postId: string } }) {
try {
// 쿠키에서 액세스 토큰 가져오기
const accessToken = cookies().get("accessToken")?.value;

if (!accessToken) {
Expand All @@ -15,12 +14,16 @@ export async function POST(request: Request, { params }: { params: { postId: str

const postId = params.postId;

// 게시글 좋아요 요청
const response = await apiClient.post(`/posts/${postId}/like`, null, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
// body를 빈 객체로 전송
const response = await apiClient.post(
`/posts/${postId}/like`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);

return NextResponse.json(response.data);
} catch (error: unknown) {
Expand All @@ -37,7 +40,6 @@ export async function POST(request: Request, { params }: { params: { postId: str
// 게시글 좋아요 취소 API
export async function DELETE(request: Request, { params }: { params: { postId: string } }) {
try {
// 쿠키에서 액세스 토큰 가져오기
const accessToken = cookies().get("accessToken")?.value;

if (!accessToken) {
Expand All @@ -46,7 +48,6 @@ export async function DELETE(request: Request, { params }: { params: { postId: s

const postId = params.postId;

// 게시글 좋아요 취소 요청
const response = await apiClient.delete(`/posts/${postId}/like`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Expand Down
14 changes: 12 additions & 2 deletions src/app/api/posts/[postId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ import apiClient from "@/lib/apiClient";
export async function GET(request: Request, { params }: { params: { postId: string } }) {
try {
const postId = params.postId;
const accessToken = cookies().get("accessToken")?.value;

// 게시글 상세 조회 요청
const response = await apiClient.get(`/posts/${postId}`);
// 로그인한 유저의 경우 토큰과 함께 요청
if (accessToken) {
const response = await apiClient.get(`/posts/${postId}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return NextResponse.json(response.data);
}

// 로그인하지 않은 유저의 경우 토큰 없이 요청
const response = await apiClient.get(`/posts/${postId}`);
return NextResponse.json(response.data);
} catch (error: unknown) {
if (error instanceof AxiosError) {
Expand Down
16 changes: 9 additions & 7 deletions src/app/components/card/cardList/MyApplicationListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ const MyApplicationListItem = ({ id, createdAt, status, resumeId, resumeName, fo
<span>|</span>
<span>{formatLocalDate(createdAt, true)}</span>
</div>
<button
type="button"
onClick={handleResumeDownload}
className="decoration-grayscale-600/50 hover:text-grayscale-600 hover:decoration-grayscale-600 text-sm font-medium text-grayscale-500 underline decoration-1 underline-offset-4 hover:cursor-pointer md:text-base"
>
이력서 보기
</button>
{resumeId > 0 && (
<button
type="button"
onClick={handleResumeDownload}
className="decoration-grayscale-600/50 hover:text-grayscale-600 hover:decoration-grayscale-600 text-sm font-medium text-grayscale-500 underline decoration-1 underline-offset-4 hover:cursor-pointer md:text-base"
>
이력서 보기
</button>
)}
</div>

{/* 중앙 컨텐츠 영역: 가게 정보, 제목, 설명 */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const ImageInputPlaceHolder: React.FC<ImageInputPlaceHolderProps> = ({
const fileInputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
setImageList(initialImages);
if (JSON.stringify(imageList) !== JSON.stringify(initialImages)) {
setImageList(initialImages);
}
}, [initialImages]);

const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
46 changes: 30 additions & 16 deletions src/app/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,41 +61,55 @@ const Pagination = ({ totalPage, currentPage, onPageChange }: PaginationProps):

return (
<nav className="flex gap-1" aria-label="페이지네이션">
<button onClick={handleClickPrevBtn} disabled={prevDisabled} aria-label="이전 페이지">
<PaginationBtn extraStyle="mr-[6px]" disabled={prevDisabled}>
<IoIosArrowBack className={cn(prevDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</button>
<PaginationBtn
onClick={handleClickPrevBtn}
disabled={prevDisabled}
extraStyle="mr-[6px]"
aria-label="이전 페이지"
>
<IoIosArrowBack className={cn(prevDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>

<ul className="flex gap-1">
{pageList.map((page) => (
<li key={page}>
<button
<PaginationBtn
onClick={() => handleChangePage(page)}
extraStyle={page === currentPage ? activeStyle : ""}
aria-label={`${page}페이지`}
aria-current={page === currentPage}
>
<PaginationBtn extraStyle={page === currentPage ? activeStyle : ""}>{page}</PaginationBtn>
</button>
{page}
</PaginationBtn>
</li>
))}
</ul>

{totalPage > maxPageShow + 2 && lastPage < totalPage - 1 && (
<>
<li>
<PaginationBtn>...</PaginationBtn>
</li>
<li>
<button onClick={() => handleChangePage(totalPage)} aria-label={`${totalPage}페이지`}>
<PaginationBtn extraStyle={totalPage === currentPage ? activeStyle : ""}>{totalPage}</PaginationBtn>
</button>
<PaginationBtn
onClick={() => handleChangePage(totalPage)}
extraStyle={totalPage === currentPage ? activeStyle : ""}
aria-label={`${totalPage}페이지`}
>
{totalPage}
</PaginationBtn>
</li>
</>
)}
<button onClick={handleClickNextBtn} disabled={nextDisabled} aria-label="다음 페이지">
<PaginationBtn extraStyle="ml-[6px]" disabled={nextDisabled}>
<IoIosArrowForward className={cn(nextDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</button>

<PaginationBtn
onClick={handleClickNextBtn}
disabled={nextDisabled}
extraStyle="ml-[6px]"
aria-label="다음 페이지"
>
<IoIosArrowForward className={cn(nextDisabled ? defaultStyle : activeStyle)} />
</PaginationBtn>
</nav>
);
};
Expand Down
Loading
Loading