Skip to content

✨ 지원자 현황 접근 상태별 퍼널 개선 - #612

Merged
manNomi merged 5 commits into
mainfrom
feat/application-status-access-funnel
Aug 1, 2026
Merged

✨ 지원자 현황 접근 상태별 퍼널 개선#612
manNomi merged 5 commits into
mainfrom
feat/application-status-access-funnel

Conversation

@manNomi

@manNomi manNomi commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

관련 이슈

작업 내용

지원자 현황 접근 상태 분리

지원자 현황을 상위 컨테이너에서 권한 상태로 판정하고 아래 4개 페이지 컴포넌트로 분리했습니다.

상태 호출 API 화면
비로그인 지원 관련 API 호출 없음 안전한 더미 미리보기 + 로그인 CTA
로그인 + 성적/어학 미제출 GET /applications/preview 자신의 모교 기준 지원 대학 공개 정보 + 지원자 수 마스킹
로그인 + 성적/어학 제출·미승인 GET /applications/preview 동일한 제한 화면 + 승인 대기 토스트
로그인 + 성적/어학 모두 승인 GET /applications 기존 전체 지원자 현황

제한 상태에서는 전체 지원자 API를 호출하지 않으며, 실제 지원자 수를 DOM에 넣고 CSS로 가리는 방식도 사용하지 않습니다. 화면에는 고정 마스킹 토큰만 렌더링됩니다.

Preview 응답 계약과 조회 범위

GET /applications/preview 요청에는 대학 ID를 보내지 않습니다. 서버가 인증된 사용자의 homeUniversityId를 기준으로 응답을 제한하므로, 경희대학교 사용자에게는 경희대학교에 연결된 대학만 반환됩니다.

클라이언트는 다음 공개 필드만 사용합니다.

  • totalUniversityCount
  • 대학 id, koreanName, studentCapacity, region, country
  • logoImageUrl, backgroundImageUrl

지원자 배열, 대학별 지원자 수, 닉네임, 성적은 응답에 없습니다. 모교가 아직 등록되지 않은 사용자는 서버에서 빈 프리뷰를 받습니다.

AS-IS / TO-BE

AS-IS TO-BE
지원자 현황 진입 즉시 로그인 페이지로 이동 현황 미리보기를 먼저 보여주고 CTA로 로그인 유도
AS-IS TO-BE

상태별 UI

아래 이미지는 기획 기준(Figma)으로 정리한 네 상태의 UI 캡처입니다.

미로그인 로그인 + 성적/어학 미제출
미로그인 로그인 + 성적/어학 미제출
로그인 + 성적/어학 제출·미승인 로그인 + 성적/어학 모두 승인
성적/어학 제출·미승인 성적/어학 모두 승인

특이 사항

  • 서버 PR #815가 먼저 배포되어야 로그인 제한 화면에서 preview API를 정상 호출할 수 있습니다.
  • 모교 범위 제한은 서버가 처리하므로 이번 변경에 따른 클라이언트 요청 스펙 변경은 없습니다.
  • 기존 승인 완료 화면과 GET /applications 계약은 변경하지 않았습니다.

리뷰 요구사항 (선택)

  • 네 상태의 판정 기준과 호출 API가 기획 의도와 맞는지
  • 제한 화면에 실제 지원자 데이터가 포함되지 않는지
  • 대학 ID를 클라이언트가 전달하지 않고 서버 인증 정보로 제한하는 계약이 적절한지
  • 네 페이지의 데이터 요청 책임과 상위 권한 분기 구조가 적절한지

검증

  • pnpm ci:check
  • UNIVERSITY_WEB_DOMAIN=http://localhost:3001 pnpm build
  • Browser MCP: 390px / 1024px 반응형, 로그인 CTA 이동, 콘솔 오류 없음

@manNomi
manNomi requested a review from wibaek as a code owner July 29, 2026 09:24
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connect-university-web Ready Ready Preview Aug 1, 2026 4:11pm
solid-connect-web-admin Ready Ready Preview Aug 1, 2026 4:11pm
solid-connection-web Ready Ready Preview Aug 1, 2026 4:11pm

@github-actions github-actions Bot added the web label Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

  1. 접근 상태 판정 및 라우팅
    인증 상태, 소속 대학, GPA 및 언어시험 승인 상태에 따라 네 가지 화면을 렌더링합니다. 학교 이메일 인증 경로와 조회 재시도 처리를 추가했습니다.

  2. 제한 상태 화면과 미리보기 조회
    대학 미리보기 API와 React Query 훅을 추가했습니다. 비공개 대학 목록, 마스킹 값, 지역 필터, 로그인 오버레이, 승인 대기 토스트를 렌더링합니다.

  3. 승인 상태 화면
    지원 대학 데이터를 병합·집계·필터링·정렬합니다. 점수 시트와 지원 대학 정보를 렌더링합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: wibaek, yoonc01

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 지원자 현황을 접근 상태별로 분리하고 퍼널을 개선한 주요 변경 사항을 명확하게 설명합니다.
Description check ✅ Passed 작업 내용, 상태별 동작, API 계약, 특이 사항, 리뷰 요구사항, 검증 결과를 대부분 충실히 작성했습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/application-status-access-funnel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa057f73cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const SCORE_PENDING_MESSAGE = "성적 승인이 아직 안되었어요! 승인 완료 후에 지원자 수 확인이 가능합니다";

const ScorePendingApplicationStatusPage = () => {
const { data: universities = [], isLoading } = useUniversitySearch("", undefined, { useDefaultTermId: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope restricted previews to the user's home university

When a score-pending user belongs to a home university, this request omits homeUniversityId, so the public search returns the term-wide catalog rather than that university's available exchange destinations. The application flow explicitly supplies homeUniversityId for this reason, while both restricted authenticated pages currently display and count out-of-scope universities; pass the authenticated user's home-university ID through the search options.

Useful? React with 👍 / 👎.

const SignedInApplicationStatusPage = () => {
const { data: universities = [], isLoading } = useUniversitySearch("", undefined, { useDefaultTermId: true });

return <RestrictedApplicationStatusView universities={universities} isLoading={isLoading} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide the missing setup action from the signed-in state

For authenticated users with no verified home university or with either score list empty, the new resolver selects this page, but it renders only a noninteractive masked preview with no link or handler to continue setup. Previously the failed applications request sent these users into /university/application/apply, whose existing flow routes school-unverified users to verification and users without scores to score registration; this change therefore leaves those users unable to advance from the status funnel unless they discover another route manually.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx (2)

308-310: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

병합 키(id 우선)와 렌더 키(koreanName)가 불일치합니다.

uniqueScoreSheetsgetScoreSheetKey(Line 353-355)로 id를 우선 사용하므로 id가 다르고 koreanName이 같은 항목이 함께 남을 수 있고, 그때 React key가 중복돼 아코디언 열림 상태가 엉킬 수 있습니다. 동일한 키 함수를 렌더에서도 재사용하는 편이 안전합니다.

♻️ 제안 리팩터
-      {scoreSheets.map((scoreSheet, index) => (
-        <MobileScoreSheet key={scoreSheet.koreanName} scoreSheet={scoreSheet} defaultOpen={index === 0} />
-      ))}
+      {scoreSheets.map((scoreSheet, index) => (
+        <MobileScoreSheet key={getScoreSheetKey(scoreSheet)} scoreSheet={scoreSheet} defaultOpen={index === 0} />
+      ))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx`
around lines 308 - 310, Update the scoreSheets.map render in
ApprovedApplicationStatusPage to use the same getScoreSheetKey function used by
uniqueScoreSheets instead of scoreSheet.koreanName for the React key, preserving
id-first identity and preventing duplicate keys.

53-56: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

allScoreSheets의 정렬은 불필요한 중복 작업입니다.

allScoreSheets는 카운트·참여자 수 집계와 필터 입력으로만 쓰이고, 최종 정렬은 Line 75에서 다시 수행됩니다. 여기서 sortMode를 빼면 정렬 토글마다 병합·중복제거까지 재실행되는 것도 함께 사라집니다.

♻️ 제안 리팩터
-  const allScoreSheets = useMemo(
-    () => sortScoreSheets(uniqueScoreSheets(scoreChoices.flat()), sortMode),
-    [scoreChoices, sortMode],
-  );
+  const allScoreSheets = useMemo(() => uniqueScoreSheets(scoreChoices.flat()), [scoreChoices]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx`
around lines 53 - 56, Update the allScoreSheets useMemo to only merge and
deduplicate scoreChoices without calling sortScoreSheets, and remove sortMode
from its dependency array. Keep the existing final sorting at the later
rendering path unchanged, while preserving allScoreSheets as the input for
counts, participant totals, and filtering.
apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx (1)

9-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

오버레이 위치를 고정 픽셀에 의존하고 있습니다.

top-[134px], min-h-[550px], -mt-14는 배너·탭 높이나 폰트 크기가 바뀌면 바로 어긋납니다. inset-0으로 부모(main)를 덮고 justify-center로 중앙 정렬하면 레이아웃 변경에 훨씬 둔감해집니다.

♻️ 제안 리팩터
-      <div className="absolute inset-x-0 top-[134px] z-10 flex min-h-[550px] items-center bg-white/80 px-5 backdrop-blur-[3px]">
-        <div className="-mt-14 flex w-full flex-col items-center gap-4 text-center">
+      <div className="absolute inset-0 z-10 flex items-center justify-center bg-white/80 px-5 backdrop-blur-[3px]">
+        <div className="flex w-full flex-col items-center gap-4 text-center">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx`
around lines 9 - 10, GuestApplicationStatusPage의 오버레이 컨테이너에서 고정 위치·높이 값인
top-[134px]와 min-h-[550px]를 제거하고 부모 main 전체를 덮도록 inset-0을 적용하세요. 내부 콘텐츠의 -mt-14도
제거하고 justify-center을 사용해 오버레이 내용을 수직 중앙 정렬하며, 기존 가로 정렬과 간격은 유지하세요.
apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx (1)

6-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

공개 대학 프리뷰 데이터 로딩이 두 페이지에 복제되어 있습니다. 동일한 useUniversitySearch("", undefined, { useDefaultTermId: true }) 호출이 양쪽에 있어, 한쪽 인자만 바뀌면 두 퍼널의 프리뷰가 조용히 달라집니다. 공통 훅(예: usePreviewUniversities)으로 한 번만 정의하는 편이 좋습니다.

  • apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx#L6-L10: 직접 호출을 공통 훅 호출로 교체합니다.
  • apps/web/src/app/university/application/_pages/ScorePendingApplicationStatusPage.tsx#L10-L24: 동일 훅을 사용하고, 이 파일에는 토스트 관련 로직만 남깁니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx`
around lines 6 - 10, 중복된 대학 프리뷰 조회를 공통 훅으로 통합하세요.
apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx#L6-L10에서는
useUniversitySearch 직접 호출을 공통 훅(예: usePreviewUniversities) 호출로 교체하고,
apps/web/src/app/university/application/_pages/ScorePendingApplicationStatusPage.tsx#L10-L24에서도
동일 훅을 사용해 두 페이지가 같은 조회 설정을 공유하도록 하세요. ScorePendingApplicationStatusPage에는 토스트 관련
로직만 남기세요.
apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx (1)

62-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

활성 탭 표기와 실제 목록 내용이 어긋납니다.

렌더되는 목록은 공개 대학 전체인데 활성 표시는 "지원자 있는 대학"입니다. 또한 승인 화면(ApprovedApplicationStatusPage.tsx Line 224, 227)은 "N개" 접미사를 쓰는데 여기는 "N"이라 퍼널 간 문구가 달라집니다. 활성 탭을 "모든 대학"으로 옮기고 카운트 표기를 통일하면 상태 전환 시 이질감이 줄어듭니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx`
around lines 62 - 69, Update RestrictedScopeTabs so the “모든 대학” tab is active,
matching the displayed public-university list, and remove active from “지원자 있는
대학”. Standardize both university count labels to the “N개” format used by
ApprovedApplicationStatusPage, including the total count and masked count while
preserving the existing fallback behavior.
apps/web/src/app/university/application/ScorePageContent.tsx (1)

34-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

② switch에 안전그물 하나 쳐두면 마음이 편해질 것 같아요.

현재는 리터럴 유니온 4종을 다 커버해서 타입 상 문제는 없지만, 나중에 ApplicationStatusAccess에 상태가 추가되면 조용히 undefined를 반환할 수 있어요. default 분기에 never 단언을 넣어두면 향후 실수를 컴파일 타임에 잡을 수 있습니다.

🛟 제안: exhaustiveness guard
   switch (access) {
     case "guest":
       return <GuestApplicationStatusPage />;
     case "signedIn":
       return <SignedInApplicationStatusPage />;
     case "scorePending":
       return <ScorePendingApplicationStatusPage />;
     case "approved":
       return <ApprovedApplicationStatusPage />;
+    default: {
+      const _exhaustiveCheck: never = access;
+      return _exhaustiveCheck;
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/university/application/ScorePageContent.tsx` around lines 34
- 43, Update the switch on ApplicationStatusAccess in ScorePageContent to add a
default exhaustiveness guard that asserts the unhandled access value is never,
ensuring newly added statuses fail at compile time instead of returning
undefined.
apps/web/src/app/university/application/_pages/applicationStatusAccess.ts (1)

12-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

② 이 함수, 테스트로 안전벨트 매어주면 든든하겠어요.

네 가지 접근 상태를 가르는 핵심 분기 로직인데 순수 함수라 테스트하기도 쉬워요. homeUniversityId=null, 배열 중 하나만 비어있는 경우, APPROVED/PENDING/REJECTED 혼합 케이스 등을 유닛 테스트로 고정해두면 이후 회귀를 빠르게 잡을 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/university/application/_pages/applicationStatusAccess.ts`
around lines 12 - 34, 대상 함수 resolveApplicationStatusAccess의 네 가지 접근 상태 분기를 고정하는
단위 테스트를 추가하세요. 비인증 사용자, homeUniversityId가 null인 경우, GPA 또는 언어 시험 배열 하나만 비어 있는
경우, APPROVED가 없는 PENDING/REJECTED 혼합 및 두 점수가 모두 승인된 경우를 각각 검증하고, 기존 반환값인 guest,
signedIn, scorePending, approved를 보장하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx`:
- Around line 25-26: Update RestrictedApplicationStatusView so
shouldShowSkeleton depends only on isLoading, preventing an empty loaded result
from leaving the skeleton visible. Add a separate showPlaceholderRows flag for
the GuestApplicationStatusPage dummy-preview path, and render an empty-list
message for the logged-in/pending-approval path while keeping aria-busy aligned
with the loading state.

In
`@apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx`:
- Around line 50-51: Update the scoreChoices handling in
ApprovedApplicationStatusPage so loading from useGetApplicationsList does not
substitute emptyChoices before the response arrives. Keep participant counts,
tab counts, and empty-state messaging hidden or in a loading state until the
application list request completes, while preserving emptyChoices for a
completed response with no choices.

In `@apps/web/src/app/university/application/ScorePageContent.tsx`:
- Around line 18-32: Update the useGetMyGpaScore and useGetMyLanguageTestScore
queries to also consume their isError states, and handle either query failure
before calling resolveApplicationStatusAccess. Do not convert failed or
unavailable score data into empty arrays for normal access resolution; render
the existing error state or otherwise propagate a distinct failure outcome so
failed requests cannot be treated as "signedIn" or "성적 미제출".

---

Nitpick comments:
In
`@apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx`:
- Around line 62-69: Update RestrictedScopeTabs so the “모든 대학” tab is active,
matching the displayed public-university list, and remove active from “지원자 있는
대학”. Standardize both university count labels to the “N개” format used by
ApprovedApplicationStatusPage, including the total count and masked count while
preserving the existing fallback behavior.

In `@apps/web/src/app/university/application/_pages/applicationStatusAccess.ts`:
- Around line 12-34: 대상 함수 resolveApplicationStatusAccess의 네 가지 접근 상태 분기를 고정하는
단위 테스트를 추가하세요. 비인증 사용자, homeUniversityId가 null인 경우, GPA 또는 언어 시험 배열 하나만 비어 있는
경우, APPROVED가 없는 PENDING/REJECTED 혼합 및 두 점수가 모두 승인된 경우를 각각 검증하고, 기존 반환값인 guest,
signedIn, scorePending, approved를 보장하세요.

In
`@apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx`:
- Around line 308-310: Update the scoreSheets.map render in
ApprovedApplicationStatusPage to use the same getScoreSheetKey function used by
uniqueScoreSheets instead of scoreSheet.koreanName for the React key, preserving
id-first identity and preventing duplicate keys.
- Around line 53-56: Update the allScoreSheets useMemo to only merge and
deduplicate scoreChoices without calling sortScoreSheets, and remove sortMode
from its dependency array. Keep the existing final sorting at the later
rendering path unchanged, while preserving allScoreSheets as the input for
counts, participant totals, and filtering.

In
`@apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx`:
- Around line 9-10: GuestApplicationStatusPage의 오버레이 컨테이너에서 고정 위치·높이 값인
top-[134px]와 min-h-[550px]를 제거하고 부모 main 전체를 덮도록 inset-0을 적용하세요. 내부 콘텐츠의 -mt-14도
제거하고 justify-center을 사용해 오버레이 내용을 수직 중앙 정렬하며, 기존 가로 정렬과 간격은 유지하세요.

In
`@apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx`:
- Around line 6-10: 중복된 대학 프리뷰 조회를 공통 훅으로 통합하세요.
apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx#L6-L10에서는
useUniversitySearch 직접 호출을 공통 훅(예: usePreviewUniversities) 호출로 교체하고,
apps/web/src/app/university/application/_pages/ScorePendingApplicationStatusPage.tsx#L10-L24에서도
동일 훅을 사용해 두 페이지가 같은 조회 설정을 공유하도록 하세요. ScorePendingApplicationStatusPage에는 토스트 관련
로직만 남기세요.

In `@apps/web/src/app/university/application/ScorePageContent.tsx`:
- Around line 34-43: Update the switch on ApplicationStatusAccess in
ScorePageContent to add a default exhaustiveness guard that asserts the
unhandled access value is never, ensuring newly added statuses fail at compile
time instead of returning undefined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e6edce29-0eca-4eae-a48e-adc6d7107fcc

📥 Commits

Reviewing files that changed from the base of the PR and between 6667373 and aa057f7.

⛔ Files ignored due to path filters (2)
  • docs/screenshots/application-status/as-is-guest.jpg is excluded by !**/*.jpg
  • docs/screenshots/application-status/to-be-guest.jpg is excluded by !**/*.jpg
📒 Files selected for processing (8)
  • apps/web/src/apis/Scores/getLanguageTestList.ts
  • apps/web/src/app/university/application/ScorePageContent.tsx
  • apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx
  • apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/ScorePendingApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/SignedInApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/applicationStatusAccess.ts

Comment thread apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx Outdated
Comment thread apps/web/src/app/university/application/ScorePageContent.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx`:
- Around line 149-153: Update RestrictedUniversityEmptyState so its primary
message is neutral and does not claim that no applicants exist; use fixed
masking wording appropriate for the restricted state while preserving the
existing layout and secondary guidance text.

In `@apps/web/src/app/university/application/ScorePageContent.tsx`:
- Around line 47-50: Update the access-transition condition in ScorePageContent
so that, when shouldFetchScoreStatus is true, either score query’s isFetching
state keeps the spinner visible, even if cached data makes isLoading false.
Preserve the existing authentication and school-email verification checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7aa64992-647c-45c4-98c4-12c0f049951c

📥 Commits

Reviewing files that changed from the base of the PR and between f81839b and 9d6b2ea.

📒 Files selected for processing (8)
  • apps/web/src/apis/Scores/getGpaList.ts
  • apps/web/src/apis/Scores/getLanguageTestList.ts
  • apps/web/src/apis/applications/api.ts
  • apps/web/src/app/my/school-email/_lib/returnTo.ts
  • apps/web/src/app/university/application/ScorePageContent.tsx
  • apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx
  • apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/university/application/_pages/GuestApplicationStatusPage.tsx
  • apps/web/src/app/university/application/_pages/ApprovedApplicationStatusPage.tsx

Comment on lines +149 to +153
const RestrictedUniversityEmptyState = () => (
<div className="flex min-h-48 flex-col items-center justify-center rounded-lg bg-k-50 px-6 py-8 text-center">
<p className="text-k-900 typo-sb-7">아직 지원자가 있는 대학이 없어요.</p>
<p className="mt-2 text-k-500 typo-medium-3">지원 현황이 등록되면 여기에 표시됩니다.</p>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

1. 제한 상태에서 실제 지원 여부를 출력하지 마세요.

"아직 지원자가 있는 대학이 없어요."는 지원자가 없다는 실제 상태를 노출합니다. 제한 상태에서는 지원자 정보에 고정 마스킹 값만 사용하세요. 중립적인 빈 목록 문구로 바꾸세요.

수정 예시
-    <p className="text-k-900 typo-sb-7">아직 지원자가 있는 대학이 없어요.</p>
-    <p className="mt-2 text-k-500 typo-medium-3">지원 현황이 등록되면 여기에 표시됩니다.</p>
+    <p className="text-k-900 typo-sb-7">표시할 대학 정보가 없어요.</p>
+    <p className="mt-2 text-k-500 typo-medium-3">잠시 후 다시 확인해 주세요.</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const RestrictedUniversityEmptyState = () => (
<div className="flex min-h-48 flex-col items-center justify-center rounded-lg bg-k-50 px-6 py-8 text-center">
<p className="text-k-900 typo-sb-7">아직 지원자가 있는 대학이 없어요.</p>
<p className="mt-2 text-k-500 typo-medium-3">지원 현황이 등록되면 여기에 표시됩니다.</p>
</div>
const RestrictedUniversityEmptyState = () => (
<div className="flex min-h-48 flex-col items-center justify-center rounded-lg bg-k-50 px-6 py-8 text-center">
<p className="text-k-900 typo-sb-7">표시할 대학 정보가 없어요.</p>
<p className="mt-2 text-k-500 typo-medium-3">잠시 후 다시 확인해 주세요.</p>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/app/university/application/_components/RestrictedApplicationStatusView.tsx`
around lines 149 - 153, Update RestrictedUniversityEmptyState so its primary
message is neutral and does not claim that no applicants exist; use fixed
masking wording appropriate for the restricted state while preserving the
existing layout and secondary guidance text.

Comment on lines +47 to +50
if (
!isAuthInitialized ||
needsSchoolEmailVerification ||
(shouldFetchScoreStatus && (isGpaLoading || isLanguageTestLoading))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate ScorePageContent.tsx and related score hooks/mutation usage"
fd -a 'ScorePageContent\.tsx$|score|Score' . | sed -n '1,120p'

echo
echo "Show ScorePageContent outline and relevant lines"
file="$(fd 'ScorePageContent\.tsx$' . | head -n 1)"
if [ -n "$file" ]; then
  wc -l "$file"
  ast-grep outline "$file" --view expanded || true
  sed -n '1,120p' "$file" | cat -n
fi

echo
echo "Search query hook definitions and usages"
rg -n "useGetMyGpaScore|useGetMyLanguageTestScore|refetchOnMount\\s*:\\s*\"always\"|score_status|needsSchoolEmailVerification|access" -S .

Repository: solid-connection/solid-connect-web

Length of output: 32671


🏁 Script executed:

#!/bin/bash
set -euo pipefail

scores="apps/web/src/apis/Scores"
status_pages="apps/web/src/app/university/application/_pages"

echo "Score hook files"
for f in "$scores/getGpaList.ts" "$scores/getLanguageTestList.ts" "$scores/index.ts"; do
  [ -f "$f" ] || continue
  echo "--- $f"
  sed -n '1,120p' "$f" | cat -n
done

echo
echo "Application access decision files"
fd '_pages' "$status_pages" -t f | sort

access_file="$(fd 'applicationStatusAccess\.(ts|tsx)$' "$status_pages" | head -n 1)"
if [ -n "$access_file" ]; then
  echo "--- $access_file"
  wc -l "$access_file"
  ast-grep outline "$access_file" --view expanded || true
  sed -n '1,240p' "$access_file" | cat -n
fi

echo
echo "Package dependency versions"
rg -n '"(`@tanstack/react-query`|`@tanstack/query-core`|`@tanstack/react-query`)' pnpm-lock.yaml package.json */package.json -S | sed -n '1,80p' || true

Repository: solid-connection/solid-connect-web

Length of output: 4650


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Package dependency versions"
rg -n '"`@tanstack/`(query-core|react-query)"|`@tanstack/`(query-core|react-query)' package.json pnpm-lock.yaml -S | sed -n '1,120p' || true

echo
echo "Behavioral probe: React Query v5 loading/fetching semantics on refetch"
node - <<'JS'
(async () => {
  const { queryClientFactory } = require('`@tanstack/query-core`')
  const client = queryClientFactory()
  const key = ['test']

  client.setQueryData(key, { value: 'stale-approval' })
  client.resumePausedMutations()

  const promise = client.fetchQuery({
    queryKey: key,
    queryFn: async () => {
      await new Promise((resolve) => setTimeout(resolve, 80))
      return { value: 'new-rejection' }
    }
  })

  const state = client.getQueryState(key)
  console.log(JSON.stringify({
    initialStatus: state?.status,
    isLoading: state?.status === 'pending' && state?.fetchStatus === 'fetching',
    isFetching: state?.fetchStatus === 'fetching'
  }))

  return promise.catch(() => {})
})()
JS

echo
echo "Check if expected package exists in lockfile"
node - <<'JS'
const fs = require('fs')
const txt = fs.readFileSync('pnpm-lock.yaml', 'utf8', { encoding: 'utf8' })
for (const pkg of ['`@tanstack/react-query`', '`@tanstack/query-core`']) {
  const lines = txt.split(/\r?\n/).filter(line => line.startsWith('  ') || line.startsWith('    ')).filter(line => line.includes(pkg))
  console.log('---', pkg, 'count', lines.length)
  console.log(lines.slice(0, 80).join('\n'))
}
JS

Repository: solid-connection/solid-connect-web

Length of output: 1956


🌐 Web query:

TanStack Query v5 refetchOnMount always isFetching isLoading status cached data

💡 Result:

In TanStack Query v5, refetchOnMount: 'always' forces a query to refetch every time its component mounts, regardless of whether the data is considered stale [1][2]. The behavior of the status and loading indicators depends on whether the query has existing cached data: 1. isLoading and isPending: - isLoading is a derived boolean (isPending && isFetching) [1][2]. It represents a "hard loading" state, which only occurs during the initial fetch when no data is available in the cache [3][4]. - If cached data already exists, the query will be in the success status immediately upon mount, even if a refetch is triggered by refetchOnMount: 'always' [5][6]. Consequently, isLoading will be false [4]. 2. isFetching: - This flag is true whenever the query function is executing, which includes both the initial "hard" fetch and any background refetches (such as those triggered by refetchOnMount: 'always' when data is already cached) [1][4]. - Use isFetching if you want to display a non-blocking indicator (like a small spinner or "updating" text) while the background refetch happens [7][4]. 3. Status: - With cached data: The status will be 'success' [8][5]. - Without cached data: The status will be 'pending' [5][4]. In summary, if you use refetchOnMount: 'always' and data exists, your component will receive the cached data immediately (status: 'success') while simultaneously performing a background refetch (isFetching: true). If you need to distinguish between the first load and subsequent background refetches, rely on isPending for the former and isFetching for the latter [4].

Citations:


1. 성적 재검증 중에는 접근 전환을 지연하세요.

refetchOnMount: "always"는 캐시된 점수 데이터가 있으면 isLoadingfalse가 됩니다. 두 점수 조회 중 하나라도 isFetching이면 approved 페이지가 렌더링되지 않도록 스피너를 유지하세요.

수정 예시
   const {
     data: gpaScoreData,
     isLoading: isGpaLoading,
+    isFetching: isGpaFetching,
     isError: isGpaError,
     refetch: refetchGpa,
   } = useGetMyGpaScore({ enabled: shouldFetchScoreStatus, refetchOnMount: "always" });
   const {
     data: languageTestScores = [],
     isLoading: isLanguageTestLoading,
+    isFetching: isLanguageTestFetching,
     isError: isLanguageTestError,
     refetch: refetchLanguageTest,
   } = useGetMyLanguageTestScore({
@@
-    (shouldFetchScoreStatus && (isGpaLoading || isLanguageTestLoading))
+    (shouldFetchScoreStatus && (isGpaFetching || isLanguageTestFetching))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
!isAuthInitialized ||
needsSchoolEmailVerification ||
(shouldFetchScoreStatus && (isGpaLoading || isLanguageTestLoading))
if (
!isAuthInitialized ||
needsSchoolEmailVerification ||
(shouldFetchScoreStatus && (isGpaFetching || isLanguageTestFetching))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/university/application/ScorePageContent.tsx` around lines 47
- 50, Update the access-transition condition in ScorePageContent so that, when
shouldFetchScoreStatus is true, either score query’s isFetching state keeps the
spinner visible, even if cached data makes isLoading false. Preserve the
existing authentication and school-email verification checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant