Skip to content

Develop#33

Merged
tlrdmsEjrqhRdl merged 10 commits into
mainfrom
develop
Jul 3, 2026
Merged

Develop#33
tlrdmsEjrqhRdl merged 10 commits into
mainfrom
develop

Conversation

@tlrdmsEjrqhRdl

Copy link
Copy Markdown
Contributor

No description provided.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
chuijun-client Ready Ready Preview, Comment Jul 3, 2026 5:08am

@tlrdmsEjrqhRdl
tlrdmsEjrqhRdl marked this pull request as ready for review July 3, 2026 05:08
@tlrdmsEjrqhRdl
tlrdmsEjrqhRdl merged commit c8a9f94 into main Jul 3, 2026
8 of 9 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several features and improvements, including an updated classroom viewer role check for teachers and admins, category tags for problems, a new profile image edit dialog with dynamic tier progress tracking, and problem list filters. The reviewer provided valuable feedback on improving performance and accessibility: replacing useMemo with useEffect for safe object URL creation to prevent memory leaks, moving static filter arrays outside of components to avoid re-creation on render, and replacing useRouter with Next.js <Link> components for better SEO and accessibility.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +24 to +35
const previewUrl = useMemo(() => {
if (!selectedFile) return profile.profileImageUrl;
return URL.createObjectURL(selectedFile);
}, [profile.profileImageUrl, selectedFile]);

useEffect(() => {
return () => {
if (selectedFile && previewUrl.startsWith("blob:")) {
URL.revokeObjectURL(previewUrl);
}
};
}, [previewUrl, selectedFile]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

useMemo 내부에서 URL.createObjectURL을 호출하여 side-effect를 발생하는 것은 React의 렌더링 원칙에 어긋나며, 메모리 누수(Memory Leak)를 유발할 수 있는 안티패턴입니다.

React의 Concurrent Mode나 Strict Mode, 혹은 Suspense 등으로 인해 컴포넌트가 렌더링만 되고 커밋(Commit)되지 않은 채 버려질 경우, useMemo에서 생성된 Object URL은 useEffect가 실행되지 않아 URL.revokeObjectURL로 해제되지 않고 메모리에 영구히 남게 됩니다.

렌더링 단계(Render Phase)를 순수하게 유지하고 안전하게 메모리를 관리하기 위해, previewUrl을 상태(State)로 관리하고 useEffect 내부에서 Object URL을 생성 및 해제하도록 수정하는 것을 권장합니다.

  const [previewUrl, setPreviewUrl] = useState<string>(profile.profileImageUrl);

  useEffect(() => {
    if (!selectedFile) {
      setPreviewUrl(profile.profileImageUrl);
      return;
    }

    const objectUrl = URL.createObjectURL(selectedFile);
    setPreviewUrl(objectUrl);

    return () => {
      URL.revokeObjectURL(objectUrl);
    };
  }, [selectedFile, profile.profileImageUrl]);

Comment on lines +223 to +260
function ProblemSelectFilters({
selectedProblemStatus,
selectedLanguage,
onSelectProblemStatus,
onSelectLanguage,
}: {
selectedProblemStatus: string;
selectedLanguage: string;
onSelectProblemStatus: (problemStatus: string) => void;
onSelectLanguage: (language: string) => void;
}) {
const levels = ["전체"];
const problemStatuses = ["전체"];
const languages = ["전체"];

return (
<div className="border-line bg-surface grid gap-3 rounded-lg border p-2 md:grid-cols-2 xl:grid-cols-3">
<FilterSelect
label="난이도"
value={levels[0] ?? "전체"}
options={levels}
onChange={() => undefined}
/>
<FilterSelect
label="문제 상태"
value={selectedProblemStatus}
options={problemStatuses}
onChange={onSelectProblemStatus}
/>
<FilterSelect
label="언어"
value={selectedLanguage}
options={languages}
onChange={onSelectLanguage}
/>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

levels, problemStatuses, languages와 같은 정적 배열 상수가 컴포넌트 내부에서 선언되어 있어, 컴포넌트가 렌더링될 때마다 매번 새로운 배열 객체가 생성됩니다.

이러한 정적 데이터는 컴포넌트 외부로 분리하여 선언함으로써 불필요한 메모리 할당과 가비지 컬렉션(GC) 부담을 줄이는 것이 좋습니다. 또한 LEVELS[0] ?? "전체"와 같이 항상 값이 존재하는 정적 배열의 첫 번째 요소에 대한 불필요한 null 병합 연산자도 제거하여 코드를 단순화할 수 있습니다.

const LEVELS = ["전체"];
const PROBLEM_STATUSES = ["전체"];
const LANGUAGES = ["전체"];

function ProblemSelectFilters({
  selectedProblemStatus,
  selectedLanguage,
  onSelectProblemStatus,
  onSelectLanguage,
}: {
  selectedProblemStatus: string;
  selectedLanguage: string;
  onSelectProblemStatus: (problemStatus: string) => void;
  onSelectLanguage: (language: string) => void;
}) {
  return (
    <div className="border-line bg-surface grid gap-3 rounded-lg border p-2 md:grid-cols-2 xl:grid-cols-3">
      <FilterSelect
        label="난이도"
        value={LEVELS[0]}
        options={LEVELS}
        onChange={() => undefined}
      />
      <FilterSelect
        label="문제 상태"
        value={selectedProblemStatus}
        options={PROBLEM_STATUSES}
        onChange={onSelectProblemStatus}
      />
      <FilterSelect
        label="언어"
        value={selectedLanguage}
        options={LANGUAGES}
        onChange={onSelectLanguage}
      />
    </div>
  );
}

Comment on lines +3 to 9
import { useRouter } from "next/navigation";
import { useSuspenseQuery } from "@tanstack/react-query";
import { problemKeys } from "@/entities/problem/api/problem-keys";
import { EditIcon } from "@/shared/assets/EditIcon";
import { useWarmPythonRuntime } from "@/shared/lib/pyodide/warm-runtime";
import { ChevronLeftIcon } from "@/shared/assets/ChevronLeftIcon";
import { QueryBoundary, type QueryErrorFallbackProps } from "@/shared/ui/query-boundary";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

단순 페이지 이동을 위해 useRouter를 사용하는 대신, Next.js의 <Link> 컴포넌트를 사용하는 것이 웹 접근성(SEO, 스크린 리더 지원) 및 사용자 경험(새 탭에서 열기 등) 측면에서 훨씬 좋습니다.

이에 따라 불필요한 useRouter 임포트를 제거하고 next/link에서 Link를 임포트하도록 수정합니다.

Suggested change
import { useRouter } from "next/navigation";
import { useSuspenseQuery } from "@tanstack/react-query";
import { problemKeys } from "@/entities/problem/api/problem-keys";
import { EditIcon } from "@/shared/assets/EditIcon";
import { useWarmPythonRuntime } from "@/shared/lib/pyodide/warm-runtime";
import { ChevronLeftIcon } from "@/shared/assets/ChevronLeftIcon";
import { QueryBoundary, type QueryErrorFallbackProps } from "@/shared/ui/query-boundary";
import Link from "next/link";
import { useSuspenseQuery } from "@tanstack/react-query";
import { problemKeys } from "@/entities/problem/api/problem-keys";
import { EditIcon } from "@/shared/assets/EditIcon";
import { useWarmPythonRuntime } from "@/shared/lib/pyodide/warm-runtime";
import { ChevronLeftIcon } from "@/shared/assets/ChevronLeftIcon";
import { QueryBoundary, type QueryErrorFallbackProps } from "@/shared/ui/query-boundary";

Comment on lines +16 to 28
const router = useRouter();
useWarmPythonRuntime();

return (
<main className="mx-auto w-full max-w-[1440px] space-y-4 px-4 py-6 sm:px-8 xl:px-10">
<button type="button" className="text-muted flex items-center gap-1 text-base font-medium">
<button
type="button"
onClick={() => router.push("/")}
className="text-muted flex cursor-pointer items-center gap-1 text-base font-medium"
>
<ChevronLeftIcon className="size-5" />
문제 목록
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

<button> 태그와 router.push를 이용한 네비게이션 대신, 시맨틱한 <a> 태그로 렌더링되는 <Link> 컴포넌트를 사용하여 웹 표준과 접근성을 준수하도록 변경합니다.

  useWarmPythonRuntime();

  return (
    <main className="mx-auto w-full max-w-[1440px] space-y-4 px-4 py-6 sm:px-8 xl:px-10">
      <Link
        href="/"
        className="text-muted flex cursor-pointer items-center gap-1 text-base font-medium"
      >
        <ChevronLeftIcon className="size-5" />
        문제 목록
      </Link>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant