Skip to content

Commit 76a6c45

Browse files
authored
Merge pull request #247 from manNomi/refactor/applicant
Refactor/applicant
2 parents 020411d + 8959c96 commit 76a6c45

File tree

14 files changed

+274
-169
lines changed

14 files changed

+274
-169
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useRouter } from "next/navigation";
2+
3+
import { AxiosResponse } from "axios";
4+
5+
import { axiosInstance } from "@/utils/axiosInstance";
6+
7+
import { useMutation } from "@tanstack/react-query";
8+
9+
// API 함수 경로
10+
export interface UseSubmitApplicationResponse {
11+
isSuccess: boolean;
12+
}
13+
14+
export interface UseSubmitApplicationRequest {
15+
gpaScoreId: number;
16+
languageTestScoreId: number;
17+
universityChoiceRequest: {
18+
firstChoiceUniversityId: number | null;
19+
secondChoiceUniversityId: number | null;
20+
thirdChoiceUniversityId: number | null;
21+
};
22+
}
23+
24+
export const postSubmitApplication = (
25+
request: UseSubmitApplicationRequest,
26+
): Promise<AxiosResponse<UseSubmitApplicationResponse>> => axiosInstance.post("/applications", request);
27+
28+
// 타입 경로
29+
30+
const usePostSubmitApplication = () => {
31+
const router = useRouter();
32+
33+
return useMutation({
34+
// mutationFn: API 요청을 수행할 비동기 함수를 지정합니다.
35+
mutationFn: (request: UseSubmitApplicationRequest) => postSubmitApplication(request),
36+
37+
// onSuccess: API 요청이 성공했을 때 실행할 콜백 함수입니다.
38+
onSuccess: (data) => {
39+
console.log("지원이 성공적으로 완료되었습니다.", data);
40+
alert("지원이 완료되었습니다.");
41+
42+
// 성공 후, 관련된 다른 데이터들을 최신 상태로 업데이트하고 싶을 때 사용합니다.
43+
// 예를 들어, '내 지원 목록' 데이터를 다시 불러옵니다.
44+
// queryClient.invalidateQueries({ queryKey: ['myApplications'] });
45+
46+
// 지원 완료 페이지로 이동합니다.
47+
router.push("/application/complete");
48+
},
49+
50+
// onError: API 요청이 실패했을 때 실행할 콜백 함수입니다.
51+
onError: (error) => {
52+
console.error("지원 중 오류가 발생했습니다.", error);
53+
alert("지원 중 오류가 발생했습니다. 다시 시도해주세요.");
54+
},
55+
});
56+
};
57+
58+
export default usePostSubmitApplication;

src/api/reports/client/usePostReport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { AxiosResponse } from "axios";
44

55
import { axiosInstance } from "@/utils/axiosInstance";
66

7-
import { ReasonType } from "@/types/reports";
7+
import { ReportType } from "@/types/reports";
88

99
import { useMutation } from "@tanstack/react-query";
1010

1111
interface UsePostReportsRequest {
1212
targetType: "POST"; // 지금은 게시글 신고 기능만 존재
1313
targetId: number; // 신고하려는 리소스의 ID
14-
reasonType: ReasonType; // Docs 참고
14+
reportType: ReportType; // Docs 참고
1515
}
1616

1717
const postReports = async (body: UsePostReportsRequest): Promise<void> => {

src/api/score/client/queryKey.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum QueryKeys {
2+
myGpaScore = "myGpaScore",
3+
myLanguageTestScore = "myLanguageTestScore",
4+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { AxiosResponse } from "axios";
2+
3+
import { axiosInstance } from "@/utils/axiosInstance";
4+
5+
import { QueryKeys } from "./queryKey";
6+
7+
import { GpaScore } from "@/types/score";
8+
9+
import { useQuery } from "@tanstack/react-query";
10+
11+
interface UseMyGpaScoreResponse {
12+
gpaScoreStatusResponseList: GpaScore[];
13+
}
14+
15+
export const getMyGpaScore = (): Promise<AxiosResponse<UseMyGpaScoreResponse>> => axiosInstance.get("/scores/gpas");
16+
17+
const useGetMyGpaScore = () => {
18+
return useQuery({
19+
queryKey: [QueryKeys.myGpaScore],
20+
queryFn: getMyGpaScore,
21+
staleTime: Infinity, // 5분간 캐시
22+
select: (data) => data.data.gpaScoreStatusResponseList,
23+
});
24+
};
25+
26+
export default useGetMyGpaScore;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { AxiosResponse } from "axios";
2+
3+
import { axiosInstance } from "@/utils/axiosInstance";
4+
5+
import { QueryKeys } from "./queryKey";
6+
7+
import { LanguageTestScore } from "@/types/score";
8+
9+
import { useQuery } from "@tanstack/react-query";
10+
11+
interface UseGetMyLanguageTestScoreResponse {
12+
languageTestScoreStatusResponseList: LanguageTestScore[];
13+
}
14+
15+
export const getMyLanguageTestScore = (): Promise<AxiosResponse<UseGetMyLanguageTestScoreResponse>> =>
16+
axiosInstance.get("/scores/language-tests");
17+
18+
const useGetMyLanguageTestScore = () => {
19+
return useQuery({
20+
queryKey: [QueryKeys.myLanguageTestScore],
21+
queryFn: getMyLanguageTestScore,
22+
staleTime: Infinity,
23+
select: (data) => data.data.languageTestScoreStatusResponseList,
24+
});
25+
};
26+
27+
export default useGetMyLanguageTestScore;

src/app/application/ScorePageContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { REGIONS_KO } from "@/constants/university";
1616
import { ApplicationListResponse } from "@/types/application";
1717
import { RegionKo } from "@/types/university";
1818

19-
import { getApplicationListApi, getCompetitorsApplicationListApi } from "@/api/application";
19+
import { getCompetitorsApplicationListApi } from "@/api/application";
2020

2121
const PREFERENCE_CHOICE: string[] = ["1순위", "2순위", "3순위"];
2222

src/app/application/apply/ApplyPageContent.tsx

Lines changed: 66 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,39 @@
11
"use client";
22

33
import { useRouter } from "next/navigation";
4-
import { useEffect, useState } from "react";
4+
import { useState } from "react";
55

66
import TopDetailNavigation from "@/components/layout/TopDetailNavigation";
77
import ProgressBar from "@/components/ui/ProgressBar";
88

99
import ConfirmStep from "./ConfirmStep";
1010
import DoneStep from "./DoneStep";
11+
import EmptyGPA from "./EmptyGPA";
1112
import GpaStep from "./GpaStep";
1213
import LanguageStep from "./LanguageStep";
1314
import UniversityStep from "./UniversityStep";
1415

15-
import { GpaScore, LanguageTestScore } from "@/types/score";
1616
import { ListUniversity } from "@/types/university";
1717

18-
import { postApplicationApi } from "@/api/application";
19-
import { getMyGpaScoreApi, getMyLanguageTestScoreApi } from "@/api/score";
20-
import { getUniversityListPublicApi } from "@/api/university";
18+
import usePostSubmitApplication from "@/api/applications/client/usePostSubmitApplication";
19+
import useGetMyGpaScore from "@/api/score/client/useGetMyGpaScore";
20+
import useGetMyLanguageTestScore from "@/api/score/client/useGetMyLanguageTestScore";
21+
import useGetUniversitySearchByText from "@/api/university/client/useGetUniversitySearchByText";
2122

2223
const ApplyPageContent = () => {
2324
const router = useRouter();
2425
const [step, setStep] = useState<number>(1);
2526

26-
const [languageTestScoreList, setLanguageTestScoreList] = useState<LanguageTestScore[]>([]);
27-
const [gpaScoreList, setGpaScoreList] = useState<GpaScore[]>([]);
28-
const [universityList, setUniversityList] = useState<ListUniversity[]>([]);
27+
const { data: universityList = [] } = useGetUniversitySearchByText("");
28+
29+
const { data: gpaScoreList = [] } = useGetMyGpaScore();
30+
const { data: languageTestScoreList = [] } = useGetMyLanguageTestScore();
31+
const { mutate: postSubmitApplication } = usePostSubmitApplication();
2932

3033
const [curLanguageTestScore, setCurLanguageTestScore] = useState<number | null>(null);
3134
const [curGpaScore, setCurGpaScore] = useState<number | null>(null);
3235
const [curUniversityList, setCurUniversityList] = useState<number[]>([]);
3336

34-
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
35-
36-
useEffect(() => {
37-
const fetchAll = async () => {
38-
try {
39-
const [gpaRes, languageRes, universityRes] = await Promise.all([
40-
getMyGpaScoreApi(),
41-
getMyLanguageTestScoreApi(),
42-
getUniversityListPublicApi(),
43-
]);
44-
setGpaScoreList(
45-
gpaRes.data.gpaScoreStatusResponseList.filter((score: GpaScore) => score.verifyStatus === "APPROVED"),
46-
);
47-
setLanguageTestScoreList(
48-
languageRes.data.languageTestScoreStatusResponseList.filter(
49-
(score: LanguageTestScore) => score.verifyStatus === "APPROVED",
50-
),
51-
);
52-
53-
// 대학명을 지역/나라, 대학명 가나다 순으로 정렬합니다
54-
const sortedUniversityList = [...universityRes.data].sort((a, b) => {
55-
// 1) region 비교
56-
const regionCompare = a.region.localeCompare(b.region);
57-
if (regionCompare !== 0) return regionCompare;
58-
59-
// 2) country 비교
60-
const countryCompare = a.country.localeCompare(b.country);
61-
if (countryCompare !== 0) return countryCompare;
62-
63-
// 3) 같은 region, country라면 대학명을 비교(가나다 순)
64-
return a.koreanName.localeCompare(b.koreanName);
65-
});
66-
setUniversityList(sortedUniversityList);
67-
} catch (err) {
68-
if (err.response) {
69-
console.error("Axios response error", err.response);
70-
if (err.response.status === 401 || err.response.status === 403) {
71-
alert("로그인이 필요합니다");
72-
document.location.href = "/login";
73-
} else {
74-
alert(err.response.data?.message);
75-
}
76-
} else {
77-
console.error("Error", err.message);
78-
alert(err.message);
79-
}
80-
}
81-
};
82-
fetchAll();
83-
}, []);
84-
8537
// 다음 스텝으로 넘어가기
8638
const goNextStep = () => setStep((prev) => prev + 1);
8739
// 이전 스텝으로 돌아가기
@@ -108,69 +60,68 @@ const ApplyPageContent = () => {
10860
return;
10961
}
11062

111-
if (isSubmitting) return;
112-
setIsSubmitting(true); // TODO: 현재 임시 submit 처리, 이후에 통합 처리 추가
113-
try {
114-
await postApplicationApi({
115-
gpaScoreId: curGpaScore,
116-
languageTestScoreId: curLanguageTestScore,
117-
universityChoiceRequest: {
118-
firstChoiceUniversityId: curUniversityList[0] || null,
119-
secondChoiceUniversityId: curUniversityList[1] || null,
120-
thirdChoiceUniversityId: curUniversityList[2] || null,
121-
},
122-
});
123-
setStep(99);
124-
} catch (err) {
125-
alert(err.response.data.message);
126-
} finally {
127-
setIsSubmitting(false);
128-
}
63+
postSubmitApplication({
64+
gpaScoreId: curGpaScore,
65+
languageTestScoreId: curLanguageTestScore,
66+
universityChoiceRequest: {
67+
firstChoiceUniversityId: curUniversityList[0] || null,
68+
secondChoiceUniversityId: curUniversityList[1] || null,
69+
thirdChoiceUniversityId: curUniversityList[2] || null,
70+
},
71+
});
72+
setStep(99);
12973
};
13074

75+
const isDataExist = gpaScoreList.length === 0 || languageTestScoreList.length === 0;
13176
return (
13277
<>
13378
<TopDetailNavigation title="지원하기" handleBack={goPrevStep} />
134-
<div className="px-5">
135-
{(step === 1 || step === 2 || step === 3) && <ProgressBar currentStep={step} totalSteps={3} />}
79+
<div className="mt-1 px-5">
80+
{(step === 1 || step === 2 || step === 3) && <ProgressBar currentStep={step} totalSteps={4} />}
13681
</div>
137-
{step === 1 && (
138-
<LanguageStep
139-
languageTestScoreList={languageTestScoreList}
140-
curLanguageTestScore={curLanguageTestScore}
141-
setCurLanguageTestScore={setCurLanguageTestScore}
142-
onNext={goNextStep}
143-
/>
144-
)}
145-
{step === 2 && (
146-
<GpaStep
147-
gpaScoreList={gpaScoreList}
148-
curGpaScore={curGpaScore}
149-
setCurGpaScore={setCurGpaScore}
150-
onNext={goNextStep}
151-
/>
152-
)}
153-
{step === 3 && (
154-
<UniversityStep
155-
universityList={universityList}
156-
curUniversityList={curUniversityList}
157-
setCurUniversityList={setCurUniversityList}
158-
onNext={goNextStep}
159-
/>
160-
)}
161-
{step === 4 && (
162-
<ConfirmStep
163-
languageTestScore={languageTestScoreList.find((score) => score.id === curLanguageTestScore)}
164-
gpaScore={gpaScoreList.find((score) => score.id === curGpaScore)}
165-
universityList={
166-
curUniversityList
167-
.map((id) => universityList.find((university) => university.id === id))
168-
.filter(Boolean) as ListUniversity[]
169-
}
170-
onNext={handleSubmit}
171-
/>
82+
{isDataExist ? (
83+
<EmptyGPA />
84+
) : (
85+
<>
86+
{step === 1 && (
87+
<LanguageStep
88+
languageTestScoreList={languageTestScoreList}
89+
curLanguageTestScore={curLanguageTestScore}
90+
setCurLanguageTestScore={setCurLanguageTestScore}
91+
onNext={goNextStep}
92+
/>
93+
)}
94+
{step === 2 && (
95+
<GpaStep
96+
gpaScoreList={gpaScoreList}
97+
curGpaScore={curGpaScore}
98+
setCurGpaScore={setCurGpaScore}
99+
onNext={goNextStep}
100+
/>
101+
)}
102+
{step === 3 && (
103+
<UniversityStep
104+
universityList={universityList}
105+
curUniversityList={curUniversityList}
106+
setCurUniversityList={setCurUniversityList}
107+
onNext={goNextStep}
108+
/>
109+
)}
110+
{step === 4 && (
111+
<ConfirmStep
112+
languageTestScore={languageTestScoreList.find((score) => score.id === curLanguageTestScore)}
113+
gpaScore={gpaScoreList.find((score) => score.id === curGpaScore)}
114+
universityList={
115+
curUniversityList
116+
.map((id) => universityList.find((university) => university.id === id))
117+
.filter(Boolean) as ListUniversity[]
118+
}
119+
onNext={handleSubmit}
120+
/>
121+
)}
122+
{step === 99 && <DoneStep />}
123+
</>
172124
)}
173-
{step === 99 && <DoneStep />}
174125
</>
175126
);
176127
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Link from "next/link";
2+
3+
import { IconSolidConnectionSmallLogo } from "@/public/svgs/my";
4+
5+
const EmptyGPA = () => {
6+
return (
7+
<div className="mt-52 flex flex-col items-center justify-center gap-2 rounded-lg text-center">
8+
<IconSolidConnectionSmallLogo />
9+
<p className="text-k-300">
10+
지원할 수 있는 성적이 없어요.
11+
<br />
12+
성적부터 입력해 볼까요?
13+
</p>
14+
<Link
15+
href="/score"
16+
className="h- mt-2 h-13 w-60 rounded-full bg-gradient-to-l from-primary to-secondary p-4 text-sm font-semibold text-white"
17+
>
18+
성적 등록하러가기
19+
</Link>
20+
</div>
21+
);
22+
};
23+
export default EmptyGPA;

0 commit comments

Comments
 (0)