diff --git a/src/app/(pages)/(albaform)/apply/[formId]/page.tsx b/src/app/(pages)/(albaform)/apply/[formId]/page.tsx index 4b0a1d99..ffa9dc0e 100644 --- a/src/app/(pages)/(albaform)/apply/[formId]/page.tsx +++ b/src/app/(pages)/(albaform)/apply/[formId]/page.tsx @@ -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"; @@ -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]"; diff --git a/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx b/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx index 3b8c411d..5fd2debc 100644 --- a/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx +++ b/src/app/(pages)/albaTalk/[albatalkId]/sections/PostDetailSection.tsx @@ -45,6 +45,7 @@ export function PostDetailSection({ postId }: { postId: string }) { onConfirm: () => {}, onCancel: () => {}, }); + router.push("/albatalk"); }, }); }, @@ -82,12 +83,48 @@ export function PostDetailSection({ postId }: { postId: string }) { {/* Author Info */} -
- User Icon +
- {post?.writer.nickname} - | - {formatLocalDate(post?.createdAt || new Date())} + User Icon +
+ {post?.writer.nickname} + | + {formatLocalDate(post?.createdAt || new Date())} +
+
+
+
+ Comment Icon + {post?.commentCount} +
+
+ Like Icon + {post?.likeCount} +
@@ -111,36 +148,6 @@ export function PostDetailSection({ postId }: { postId: string }) {
) : null} - - {/* Footer */} -
-
- Comment Icon - {post?.commentCount} -
-
- Like Icon - {post?.likeCount} -
-
); } diff --git a/src/app/api/posts/[postId]/like/route.ts b/src/app/api/posts/[postId]/like/route.ts index b2419865..a269cab1 100644 --- a/src/app/api/posts/[postId]/like/route.ts +++ b/src/app/api/posts/[postId]/like/route.ts @@ -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) { @@ -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) { @@ -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) { @@ -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}`, diff --git a/src/app/api/posts/[postId]/route.ts b/src/app/api/posts/[postId]/route.ts index ec76f32e..8f423708 100644 --- a/src/app/api/posts/[postId]/route.ts +++ b/src/app/api/posts/[postId]/route.ts @@ -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) { diff --git a/src/app/components/card/cardList/MyApplicationListItem.tsx b/src/app/components/card/cardList/MyApplicationListItem.tsx index f95cb507..c400767a 100644 --- a/src/app/components/card/cardList/MyApplicationListItem.tsx +++ b/src/app/components/card/cardList/MyApplicationListItem.tsx @@ -79,13 +79,15 @@ const MyApplicationListItem = ({ id, createdAt, status, resumeId, resumeName, fo | {formatLocalDate(createdAt, true)} - + {resumeId > 0 && ( + + )} {/* 중앙 컨텐츠 영역: 가게 정보, 제목, 설명 */} diff --git a/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx b/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx index 4ec6202e..e2b4a3f7 100644 --- a/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx +++ b/src/app/components/input/file/ImageInput/ImageInputPlaceHolder.tsx @@ -28,7 +28,9 @@ const ImageInputPlaceHolder: React.FC = ({ const fileInputRef = useRef(null); useEffect(() => { - setImageList(initialImages); + if (JSON.stringify(imageList) !== JSON.stringify(initialImages)) { + setImageList(initialImages); + } }, [initialImages]); const handleFileChange = async (event: React.ChangeEvent) => { diff --git a/src/app/components/pagination/Pagination.tsx b/src/app/components/pagination/Pagination.tsx index 4abea41d..bae1f139 100644 --- a/src/app/components/pagination/Pagination.tsx +++ b/src/app/components/pagination/Pagination.tsx @@ -61,41 +61,55 @@ const Pagination = ({ totalPage, currentPage, onPageChange }: PaginationProps): return ( ); }; diff --git a/src/app/components/pagination/paginationComponent/PaginationBtn.tsx b/src/app/components/pagination/paginationComponent/PaginationBtn.tsx index 51249823..46b56650 100644 --- a/src/app/components/pagination/paginationComponent/PaginationBtn.tsx +++ b/src/app/components/pagination/paginationComponent/PaginationBtn.tsx @@ -1,21 +1,30 @@ import { cn } from "@/lib/tailwindUtil"; -const PaginationBtn = ({ - children, - extraStyle, - disabled, -}: { +interface PaginationBtnProps { children: React.ReactNode; - extraStyle?: string; + onClick?: () => void; disabled?: boolean; -}) => { - const wrapperStyle = - "size-[34px] lg:radius-lg flex items-center justify-center rounded-md lg:size-[48px] bg-background-200"; - const textStyle = "leading-[24px] lg:text-lg text-sm lg:leading-[26px]"; - const defaultStyle = "text-grayscale-200 font-medium lg:font-normal"; + extraStyle?: string; + "aria-label"?: string; + "aria-current"?: boolean; +} +const PaginationBtn = ({ + children, + onClick, + disabled, + extraStyle, + "aria-label": ariaLabel, + "aria-current": ariaCurrent, +}: PaginationBtnProps) => { return ( - ); diff --git a/src/app/globals.css b/src/app/globals.css index 8e6b409f..5728264f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -18,23 +18,20 @@ /* 넥슨 폰트 */ @font-face { - font-family: "NEXON Lv1 Gothic OTF Light"; - src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Light.woff") - format("woff"); - font-weight: 300; -} -@font-face { - font-family: "NEXON Lv1 Gothic OTF Regular"; - src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Regular.woff") - format("woff"); + font-family: "NEXON Lv1 Gothic OTF"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF.woff") format("woff"); font-weight: 400; + font-style: normal; + font-display: swap; } + @font-face { - font-family: "NEXON Lv1 Gothic OTF Bold"; - src: url("https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Bold.woff") + font-family: "NEXON Lv1 Gothic OTF"; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/NEXON Lv1 Gothic OTF Bold.woff") format("woff"); font-weight: 700; font-style: normal; + font-display: swap; } /* 기본적인 스타일을 정의 */ @@ -114,7 +111,7 @@ input[type="radio"].radio-custom:checked { box-shadow: 0 0 0 1.6px #fbaf37; } -/* 체크박스 기본 스타일 */ +/* 체크박스 본 스타일 */ input[type="checkbox"] { appearance: none; width: 16px; @@ -330,3 +327,9 @@ input[type="checkbox"]:disabled { .bg-black50 { @apply !bg-[#141414] !bg-opacity-50; } + +@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css"); + +/* 또는 */ + +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap"); diff --git a/src/hooks/queries/post/useAddPost.ts b/src/hooks/queries/post/useAddPost.ts index c63c207a..2b56efe7 100644 --- a/src/hooks/queries/post/useAddPost.ts +++ b/src/hooks/queries/post/useAddPost.ts @@ -7,7 +7,7 @@ import { PostSchema } from "@/schemas/postSchema"; export const useAddPost = () => { const queryClient = useQueryClient(); - const mutation = useMutation({ + const mutation = useMutation({ mutationFn: async (data: PostSchema) => { const response = await axios.post("/api/posts", data, { headers: { @@ -17,7 +17,7 @@ export const useAddPost = () => { }); return response.data; }, - onSuccess: () => { + onSuccess: (data) => { toast.success("게시글이 등록되었습니다!", { style: { textAlign: "center", @@ -25,6 +25,7 @@ export const useAddPost = () => { }); // 게시글 목록 캐시 무효화 queryClient.invalidateQueries({ queryKey: ["posts"] }); + return data; // 응답 데이터 반환 }, onError: (error) => { if (axios.isAxiosError(error)) { @@ -41,6 +42,7 @@ export const useAddPost = () => { }, }); } + throw error; // 에러를 다시 throw하여 컴포넌트에서 처리할 수 있도록 함 }, }); diff --git a/tailwind.config.ts b/tailwind.config.ts index 90188a0a..002749dc 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -56,18 +56,9 @@ const config: Config = { }, }, fontFamily: { - nexon: [ - "NEXON Lv1 Gothic OTF", - "sans-serif", - "-apple-system", - "BlinkMacSystemFont", - "system-ui", - "Helvetica Neue", - "Apple SD Gothic Neo", - "sans-serif", - "sans-serif", - ], + nexon: ["NEXON Lv1 Gothic OTF", "sans-serif"], school: ["HakgyoansimDunggeunmisoTTF-R", "HakgyoansimDunggeunmisoTTF-B", "sans-serif"], + sans: ["Pretendard", "sans-serif"], }, }, },