From 4aae47a9596984d9e6b406f9cd44a4ac123503e1 Mon Sep 17 00:00:00 2001 From: yoonc01 Date: Sat, 27 Jun 2026 01:27:14 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=ED=95=99=EC=A0=90=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=EC=97=90=20=EB=AA=A8=ED=95=99=EA=B5=90=EB=AA=85=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/apis/Scores/api.ts | 1 + apps/web/src/apis/Scores/getGpaList.ts | 5 ++-- .../src/app/my/school-email/_lib/returnTo.ts | 19 +++++++++++++ .../SchoolEmailVerificationContent/index.tsx | 7 ++--- .../application/apply/ApplyPageContent.tsx | 12 ++++++--- .../university/application/apply/GpaStep.tsx | 5 ++-- .../src/app/university/score/ScoreScreen.tsx | 27 ++++++++++++++++--- .../score/submit/gpa/GpaSubmitForm.tsx | 19 ++++++++++++- apps/web/src/types/score.ts | 1 + 9 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 apps/web/src/app/my/school-email/_lib/returnTo.ts diff --git a/apps/web/src/apis/Scores/api.ts b/apps/web/src/apis/Scores/api.ts index e19619394..b9b1d07bf 100644 --- a/apps/web/src/apis/Scores/api.ts +++ b/apps/web/src/apis/Scores/api.ts @@ -10,6 +10,7 @@ export const ScoresQueryKeys = { // ====== Types ====== export interface UseMyGpaScoreResponse { + homeUniversityName: string; gpaScoreStatusResponseList: GpaScore[]; } diff --git a/apps/web/src/apis/Scores/getGpaList.ts b/apps/web/src/apis/Scores/getGpaList.ts index 00437ae8c..e2e43eb5c 100644 --- a/apps/web/src/apis/Scores/getGpaList.ts +++ b/apps/web/src/apis/Scores/getGpaList.ts @@ -5,12 +5,13 @@ import { ScoresQueryKeys, scoresApi } from "./api"; /** * @description 내 학점 점수 조회 훅 */ -const useGetMyGpaScore = () => { +const useGetMyGpaScore = ({ enabled = true }: { enabled?: boolean } = {}) => { return useQuery({ queryKey: [ScoresQueryKeys.myGpaScore], queryFn: scoresApi.getMyGpaScore, + enabled, staleTime: Infinity, - select: (data) => data.data.gpaScoreStatusResponseList, + select: (data) => data.data, }); }; diff --git a/apps/web/src/app/my/school-email/_lib/returnTo.ts b/apps/web/src/app/my/school-email/_lib/returnTo.ts new file mode 100644 index 000000000..989c76a35 --- /dev/null +++ b/apps/web/src/app/my/school-email/_lib/returnTo.ts @@ -0,0 +1,19 @@ +export const SCHOOL_EMAIL_RETURN_PATHS = { + applicationApply: "/university/application/apply", + score: "/university/score", + gpaSubmit: "/university/score/submit/gpa", +} as const; + +export type SchoolEmailReturnTo = keyof typeof SCHOOL_EMAIL_RETURN_PATHS; + +export const getSchoolEmailReturnPath = (returnTo: string | null | undefined) => { + if (!returnTo || !(returnTo in SCHOOL_EMAIL_RETURN_PATHS)) { + return "/"; + } + + return SCHOOL_EMAIL_RETURN_PATHS[returnTo as SchoolEmailReturnTo]; +}; + +export const getSchoolEmailVerificationPath = (returnTo: SchoolEmailReturnTo) => { + return `/my/school-email?returnTo=${returnTo}`; +}; diff --git a/apps/web/src/app/my/school-email/_ui/SchoolEmailVerificationContent/index.tsx b/apps/web/src/app/my/school-email/_ui/SchoolEmailVerificationContent/index.tsx index d10a070be..7edcea215 100644 --- a/apps/web/src/app/my/school-email/_ui/SchoolEmailVerificationContent/index.tsx +++ b/apps/web/src/app/my/school-email/_ui/SchoolEmailVerificationContent/index.tsx @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/Inputa"; import { Label } from "@/components/ui/Label"; import { Progress } from "@/components/ui/Progress"; import { IconExpRed } from "@/public/svgs/ui"; +import { getSchoolEmailReturnPath } from "../../_lib/returnTo"; const VERIFICATION_LIMIT_SECONDS = 300; const CODE_LENGTH = 6; @@ -43,7 +44,7 @@ const SchoolEmailVerificationContent = () => { const isCodeInputValid = code.length === CODE_LENGTH; const isTimerExpired = remainingSeconds <= 0; const progressValue = step === "email" ? 0 : step === "code" ? 50 : 100; - const shouldReturnToApply = searchParams?.get("returnTo") === "applicationApply"; + const returnPath = getSchoolEmailReturnPath(searchParams?.get("returnTo")); useEffect(() => { if (step !== "code" || remainingSeconds <= 0) return; @@ -134,8 +135,8 @@ const SchoolEmailVerificationContent = () => {
- router.replace(shouldReturnToApply ? "/university/application/apply" : "/")}> - {shouldReturnToApply ? "지원하기로 돌아가기" : "홈으로"} + router.replace(returnPath)}> + {returnPath === "/" ? "홈으로" : "이전 화면으로 돌아가기"}
diff --git a/apps/web/src/app/university/application/apply/ApplyPageContent.tsx b/apps/web/src/app/university/application/apply/ApplyPageContent.tsx index 7421498cd..266cbe8ad 100644 --- a/apps/web/src/app/university/application/apply/ApplyPageContent.tsx +++ b/apps/web/src/app/university/application/apply/ApplyPageContent.tsx @@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from "react"; import { usePostSubmitApplication } from "@/apis/applications"; import { useGetMyGpaScore, useGetMyLanguageTestScore } from "@/apis/Scores"; import { useUniversitySearch } from "@/apis/universities"; +import { getSchoolEmailVerificationPath } from "@/app/my/school-email/_lib/returnTo"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import ProgressBar from "@/components/ui/ProgressBar"; import { DEFAULT_MAX_CHOICE_COUNT, getHomeUniversityById } from "@/constants/university"; @@ -36,7 +37,9 @@ const ApplyPageContent = () => { ); const { data: universityList = [] } = useUniversitySearch("", undefined, universitySearchOptions); - const { data: gpaScoreList = [] } = useGetMyGpaScore(); + const shouldFetchGpaScore = isAuthInitialized && isAuthenticated && homeUniversityId !== null; + // TODO: 서버의 모학교 미인증 에러 코드가 확정되면 GPA 조회 실패 시 학교 인증으로 보내는 fallback을 추가한다. + const { data: gpaScoreData } = useGetMyGpaScore({ enabled: shouldFetchGpaScore }); const { data: languageTestScoreList = [] } = useGetMyLanguageTestScore(); const { mutate: postSubmitApplication } = usePostSubmitApplication({ onSuccess: () => { @@ -53,7 +56,7 @@ const ApplyPageContent = () => { return; } - router.replace("/my/school-email?returnTo=applicationApply"); + router.replace(getSchoolEmailVerificationPath("applicationApply")); }, [homeUniversityId, isAuthInitialized, isAuthenticated, router]); // 다음 스텝으로 넘어가기 @@ -95,6 +98,8 @@ const ApplyPageContent = () => { }); }; + const gpaScoreList = gpaScoreData?.gpaScoreStatusResponseList ?? []; + const homeUniversityName = gpaScoreData?.homeUniversityName; const isDataExist = gpaScoreList.length === 0 || languageTestScoreList.length === 0; const hasSelectedUniversity = curUniversityList.some((universityId) => universityId > 0); const progressStep = step === 3 && hasSelectedUniversity ? APPLY_PROGRESS_TOTAL_STEPS : step + 1; @@ -123,9 +128,10 @@ const ApplyPageContent = () => { onNext={goNextStep} /> )} - {step === 2 && ( + {step === 2 && homeUniversityName && ( void; onNext: () => void; }; -const GpaStep = ({ gpaScoreList, curGpaScore, setCurGpaScore, onNext }: GpaStepProps) => { +const GpaStep = ({ gpaScoreList, homeUniversityName, curGpaScore, setCurGpaScore, onNext }: GpaStepProps) => { const [isModalOpen, setIsModalOpen] = useState(false); const handleNext = () => { @@ -50,7 +51,7 @@ const GpaStep = ({ gpaScoreList, curGpaScore, setCurGpaScore, onNext }: GpaStepP className="transition-transform hover:scale-[1.01] active:scale-[0.97]" > { const router = useRouter(); + const homeUniversityId = useAuthStore((state) => state.homeUniversityId); + const isAuthInitialized = useAuthStore((state) => state.isInitialized); + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); const [curTab, setCurTab] = useState("공인어학"); - const { data: gpaScoreList = [] } = useGetMyGpaScore(); + const shouldFetchGpaScore = isAuthInitialized && isAuthenticated && homeUniversityId !== null; + // TODO: 서버의 모학교 미인증 에러 코드가 확정되면 GPA 조회 실패 시 학교 인증으로 보내는 fallback을 추가한다. + const { data: gpaScoreData } = useGetMyGpaScore({ enabled: shouldFetchGpaScore }); const { data: languageTestScoreList = [] } = useGetMyLanguageTestScore(); + const gpaScoreList = gpaScoreData?.gpaScoreStatusResponseList ?? []; const isEmptyCurrentTab = curTab === "공인어학" ? languageTestScoreList.length === 0 : gpaScoreList.length === 0; + useEffect(() => { + if (!isAuthInitialized || !isAuthenticated || homeUniversityId !== null) { + return; + } + + router.replace(getSchoolEmailVerificationPath("score")); + }, [homeUniversityId, isAuthInitialized, isAuthenticated, router]); + const handleScoreClick = (status: ScoreSubmitStatus, rejectedReason?: string | null) => { if (status === ScoreSubmitStatus.REJECTED) { showIconToast("logo", rejectedReason ?? "승인이 거절되었습니다."); @@ -30,6 +46,10 @@ const ScoreScreen = () => { } }; + if (isAuthInitialized && isAuthenticated && homeUniversityId === null) { + return null; + } + return (
@@ -68,6 +88,7 @@ const ScoreScreen = () => { ))} {curTab === "학점" && + gpaScoreData && gpaScoreList.map((score) => (