diff --git a/src/app/[locale]/(protected)/loading.tsx b/src/app/[locale]/(protected)/loading.tsx index f549fc4f..bcadc8a1 100644 --- a/src/app/[locale]/(protected)/loading.tsx +++ b/src/app/[locale]/(protected)/loading.tsx @@ -1,4 +1,4 @@ -import { ListPageSkeleton } from '@/components/ui/skeleton'; +import { GenericPageSkeleton } from '@/components/ui/skeleton'; /** * Protected Group Loading UI @@ -7,8 +7,12 @@ import { ListPageSkeleton } from '@/components/ui/skeleton'; * - AuthenticatedLayout 내에서 표시됨 (사이드바, 헤더 유지) * - React Suspense 자동 적용 * - 페이지 전환 시 즉각적인 피드백 - * - 스켈레톤 UI로 레이아웃 유지하며 로딩 표시 + * - 범용 스켈레톤으로 모든 페이지 타입에 대응 + * + * 참고: + * - 리스트 페이지: IntegratedListTemplateV2 내부에서 상세 스켈레톤 처리 + * - 커스텀 페이지: 이 범용 스켈레톤이 표시됨 */ export default function ProtectedLoading() { - return ; + return ; } diff --git a/src/app/[locale]/(protected)/settings/account-info/page.tsx b/src/app/[locale]/(protected)/settings/account-info/page.tsx index 98c960f2..2e7fbcc1 100644 --- a/src/app/[locale]/(protected)/settings/account-info/page.tsx +++ b/src/app/[locale]/(protected)/settings/account-info/page.tsx @@ -1,9 +1,14 @@ 'use client'; import { useEffect, useState } from 'react'; +import { User } from 'lucide-react'; import { AccountInfoClient } from '@/components/settings/AccountInfoManagement'; import { getAccountInfo } from '@/components/settings/AccountInfoManagement/actions'; import type { AccountInfo, TermsAgreement, MarketingConsent } from '@/components/settings/AccountInfoManagement/types'; +import { PageLayout } from '@/components/organisms/PageLayout'; +import { PageHeader } from '@/components/organisms/PageHeader'; +import { Card, CardContent, CardHeader } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; const DEFAULT_ACCOUNT_INFO: AccountInfo = { id: '', @@ -44,9 +49,72 @@ export default function AccountInfoPage() { if (isLoading) { return ( -
-
로딩 중...
-
+ + +
+ {/* 계정 정보 카드 스켈레톤 */} + + + + + +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ {/* 약관 동의 정보 카드 스켈레톤 */} + + + + + +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+
+
+
); } diff --git a/src/app/[locale]/(protected)/subscription/page.tsx b/src/app/[locale]/(protected)/subscription/page.tsx index d3f4291f..ffe6592c 100644 --- a/src/app/[locale]/(protected)/subscription/page.tsx +++ b/src/app/[locale]/(protected)/subscription/page.tsx @@ -1,8 +1,13 @@ 'use client'; import { useEffect, useState } from 'react'; +import { CreditCard } from 'lucide-react'; import { SubscriptionManagement } from '@/components/settings/SubscriptionManagement'; import { getSubscriptionData } from '@/components/settings/SubscriptionManagement/actions'; +import { PageLayout } from '@/components/organisms/PageLayout'; +import { PageHeader } from '@/components/organisms/PageHeader'; +import { Card, CardContent } from '@/components/ui/card'; +import { Skeleton } from '@/components/ui/skeleton'; export default function SubscriptionPage() { const [data, setData] = useState>['data']>(undefined); @@ -18,9 +23,47 @@ export default function SubscriptionPage() { if (isLoading) { return ( -
-
로딩 중...
-
+ + + {/* 헤더 액션 버튼 스켈레톤 */} +
+ + +
+
+ {/* 구독 정보 카드 그리드 스켈레톤 */} +
+ {[1, 2, 3].map((i) => ( + + + + + + + ))} +
+ {/* 구독 정보 카드 스켈레톤 */} + + + + +
+ {[1, 2, 3].map((i) => ( +
+ + + +
+ ))} +
+
+
+
+
); } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx index 6cbd8b9b..a345451e 100644 --- a/src/components/ui/skeleton.tsx +++ b/src/components/ui/skeleton.tsx @@ -598,6 +598,65 @@ function PageHeaderSkeleton({ ); } +// ============================================ +// 14. 범용 페이지 스켈레톤 (커스텀 페이지용) +// ============================================ +/** + * 모든 페이지에 대응하는 심플한 범용 스켈레톤 + * - 리스트 페이지: IntegratedListTemplateV2 내부에서 처리 + * - 커스텀 페이지: 이 스켈레톤 사용 (settings, qms 등) + */ +function GenericPageSkeleton() { + return ( +
+ {/* 페이지 헤더 영역 */} +
+ +
+ + +
+
+ + {/* 액션 버튼 영역 */} +
+ + +
+ + {/* 콘텐츠 카드 영역 */} + + +
+ {Array.from({ length: 6 }).map((_, i) => ( +
+ + +
+ ))} +
+
+
+ + {/* 추가 콘텐츠 카드 */} + + + +
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ + + +
+ ))} +
+
+
+
+ ); +} + // ============================================ // Export // ============================================ @@ -615,4 +674,5 @@ export { ListPageSkeleton, PageHeaderSkeleton, ContentSkeleton, + GenericPageSkeleton, }; diff --git a/src/layouts/AuthenticatedLayout.tsx b/src/layouts/AuthenticatedLayout.tsx index f4df1dbb..246d5cbc 100644 --- a/src/layouts/AuthenticatedLayout.tsx +++ b/src/layouts/AuthenticatedLayout.tsx @@ -477,13 +477,43 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro // By removing this check, we allow the component to render immediately with default values // and update once hydration completes through the useEffect above. - // 🔧 새로고침 시 스피너 표시 (hydration 전) + // 🔧 새로고침 시 스켈레톤 표시 (hydration 전) + // GenericPageSkeleton import는 상단에 추가 필요 if (!isMounted) { return ( -
-
-
-

로딩 중...

+
+ {/* 헤더 스켈레톤 */} +
+ + {/* 사이드바 + 콘텐츠 스켈레톤 */} +
+ {/* 사이드바 스켈레톤 */} +
+
+
+ + {/* 콘텐츠 스켈레톤 */} +
+ {/* 페이지 헤더 */} +
+
+
+
+
+
+
+ {/* 콘텐츠 카드 */} +
+
+ {[1, 2, 3, 4, 5, 6].map((i) => ( +
+
+
+
+ ))} +
+
+
);