임동욱 임동욱 08-10
Merge branch 'feature/student-members' into integration/student-members
@7551625849d5621d2745896dc4d4e01cf3dd4b72
app/(protected)/(basic)/_components/admin-sidebar.tsx
--- app/(protected)/(basic)/_components/admin-sidebar.tsx
+++ app/(protected)/(basic)/_components/admin-sidebar.tsx
@@ -7,8 +7,13 @@
   href: string;
 }
 
-// 현재 정의된 화면은 홈뿐이다 — 신규 화면이 추가될 때마다 이 목록에 항목을 늘린다.
-const SIDEBAR_NAV_ITEMS: SidebarNavItem[] = [{ label: '홈', href: '/' }];
+// 신규 화면이 추가될 때마다 이 목록에 항목을 늘린다. 「학생 회원」은 기획상 「학생회원관리」
+// 그룹에 속하지만, 사이드바가 아직 flat 구조(그룹 헤딩 없음)라 지금은 평탄한 항목으로만
+// 추가한다 — 그룹 헤딩 컴포넌트 신설은 공용 컴포넌트 영역이라 design 레인 소관(§4.2)이다.
+const SIDEBAR_NAV_ITEMS: SidebarNavItem[] = [
+  { label: '홈', href: '/' },
+  { label: '학생 회원', href: '/students' },
+];
 
 interface AdminSidebarProps {
   isMobileOpen: boolean;
@@ -22,9 +27,13 @@
  * className 패스스루로 처리한다. 전부 Tailwind 반응형 클래스(CSS-first) — JS로 브레이크포인트를
  * 판별하지 않는다.
  *
- * 주의(공용 컴포넌트 부족분): SidebarLink는 아이콘 전용(라벨 숨김) variant를 제공하지 않는다.
- * 현재 유일한 내비 항목("홈")은 라벨이 1글자라 접힘 폭(w-16)에서도 시각적으로 문제없지만,
- * 라벨이 긴 항목이 추가되면 SidebarLink에 collapsed/아이콘 전용 variant 추가가 필요하다.
+ * 주의(공용 컴포넌트 부족분 — 실증됨): SidebarLink는 아이콘 전용(라벨 숨김) variant를 제공하지
+ * 않는다. "홈"은 1글자라 접힘 폭(md:w-16=4rem)에서도 문제없었지만, "학생 회원"(4글자)을 추가한
+ * 뒤 접힘 상태를 실제로 확인한 결과 라벨이 글자 단위로 세로 줄바꿈되어("학\n생\n회\n원") 링크
+ * pill이 4줄로 늘어나는 것을 확인했다(라이트/다크 동일하게 재현 — 테마 무관, 순수 레이아웃
+ * 이슈). SidebarLink에 collapsed/아이콘 전용 variant(예: 접힘 시 라벨을 툴팁으로 옮기고 아이콘만
+ * 표시) 추가가 필요하다 — design 레인 소관(§4.2 공용 컴포넌트 신설)이라 이번 범위에서는
+ * 수정하지 않고 보고만 한다.
  */
 export function AdminSidebar({
   isMobileOpen,
 
app/(protected)/(basic)/students/error.tsx (added)
+++ app/(protected)/(basic)/students/error.tsx
@@ -0,0 +1,32 @@
+'use client';
+
+import { useEffect } from 'react';
+
+interface ErrorPageProps {
+  error: Error & { digest?: string };
+  unstable_retry: () => void;
+}
+
+// 원본 메시지·스택은 화면에 내지 않는다 — 개발자 진단용으로만 콘솔에 남기고(프로덕션에서는
+// Next.js가 Server Component 에러를 이미 digest 포함 일반화 메시지로 치환해 전달한다),
+// 사용자에게는 일반화된 문구 + 재시도만 제공한다. Next.js 16.2 문서 권고에 따라 세그먼트
+// 재조회까지 수행하는 `unstable_retry()`를 `reset()` 대신 사용한다.
+export default function ErrorPage({ error, unstable_retry }: ErrorPageProps) {
+  useEffect(() => {
+    console.error(error);
+  }, [error]);
+
+  return (
+    <section className="flex flex-1 flex-col items-center justify-center gap-2 p-8">
+      <h1 className="text-lg text-foreground">문제가 발생했습니다.</h1>
+      <p className="text-sm text-foreground/60">잠시 후 다시 시도해 주세요.</p>
+      <button
+        type="button"
+        onClick={() => unstable_retry()}
+        className="mt-4 bg-foreground px-4 py-2 text-background"
+      >
+        다시 시도
+      </button>
+    </section>
+  );
+}
 
app/(protected)/(basic)/students/loading.tsx (added)
+++ app/(protected)/(basic)/students/loading.tsx
@@ -0,0 +1,12 @@
+import { Spinner } from '@/components/ui/spinner';
+
+// page.tsx가 스트리밍되는 동안의 폴백 — 같은 section 배치(flex-1 items-center justify-center)를
+// 써서 콘텐츠 교체 시 레이아웃이 흔들리지 않게 한다. Server Component로 충분해 'use client'를
+// 붙이지 않는다.
+export default function Loading() {
+  return (
+    <section className="flex flex-1 flex-col items-center justify-center gap-2 p-8">
+      <Spinner size="lg" />
+    </section>
+  );
+}
 
app/(protected)/(basic)/students/page.tsx (added)
+++ app/(protected)/(basic)/students/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from 'next';
+import { verifySession } from '@/lib/auth/dal';
+
+export const metadata: Metadata = {
+  title: '학생 회원',
+};
+
+// 목록·상세는 이번 범위 밖 — 라우트 골격만 제공한다 (§4.1 승인 범위 한정).
+export default async function Page() {
+  await verifySession();
+
+  return (
+    <section className="flex flex-1 flex-col items-center justify-center gap-2 p-8">
+      <h1 className="text-lg text-foreground">학생 회원</h1>
+      <p className="text-sm text-foreground/60">준비 중인 화면입니다.</p>
+    </section>
+  );
+}
Add a comment
List