feat: [Phase 1] 모듈 레지스트리 + 라우트 가드
- src/modules/ 모듈 시스템 (types, registry, tenant-config) - useModules() 훅: 테넌트 industry 기반 모듈 활성화 판단 - ModuleGuard: 비허용 모듈 라우트 접근 차단 (클라이언트 사이드) - (protected)/layout.tsx에 ModuleGuard 적용 - industry 미설정 테넌트는 가드 비활성 (하위 호환) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
60
src/components/auth/ModuleGuard.tsx
Normal file
60
src/components/auth/ModuleGuard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 모듈 라우트 가드
|
||||
*
|
||||
* 현재 테넌트가 보유하지 않은 모듈의 페이지에 접근 시 차단.
|
||||
* (protected)/layout.tsx에서 PermissionGate 내부에 래핑.
|
||||
*
|
||||
* Phase 1: 클라이언트 사이드 가드 (백엔드 변경 불필요)
|
||||
* Phase 2: middleware.ts로 이동 (서버 사이드 가드)
|
||||
*/
|
||||
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { useModules } from '@/hooks/useModules';
|
||||
import { ShieldAlert } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export function ModuleGuard({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { isRouteAllowed, tenantIndustry } = useModules();
|
||||
|
||||
// locale 접두사 제거 (예: /ko/production → /production)
|
||||
const cleanPath = pathname.replace(/^\/[a-z]{2}(?=\/)/, '');
|
||||
|
||||
const allowed = isRouteAllowed(cleanPath);
|
||||
|
||||
useEffect(() => {
|
||||
// industry가 아직 설정되지 않은 테넌트는 가드 비활성 (전부 허용)
|
||||
if (!tenantIndustry) return;
|
||||
|
||||
if (!allowed) {
|
||||
toast.error('접근 권한이 없는 모듈입니다.');
|
||||
}
|
||||
}, [allowed, tenantIndustry]);
|
||||
|
||||
// industry 미설정 시 가드 비활성 (하위 호환)
|
||||
if (!tenantIndustry) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
|
||||
<ShieldAlert className="h-16 w-16 text-muted-foreground mb-4" />
|
||||
<h1 className="text-2xl font-bold mb-2">접근 권한 없음</h1>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
현재 계약에 포함되지 않은 모듈입니다.
|
||||
</p>
|
||||
<Button variant="outline" onClick={() => router.push('/dashboard')}>
|
||||
대시보드로 돌아가기
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
Reference in New Issue
Block a user