Files
zellyy-finance/src/components/auth/AuthGuard.tsx
hansoo c231d5be65 feat: Clerk + Supabase 통합 시스템 구현 완료
주요 변경사항:
• Clerk 인증 시스템 통합 및 설정
• Supabase 데이터베이스 스키마 설계 및 적용
• JWT 기반 Row Level Security (RLS) 정책 구현
• 기존 Appwrite 인증을 Clerk로 완전 교체

기술적 개선:
• 무한 로딩 문제 해결 - Index.tsx 인증 로직 수정
• React root 마운팅 오류 수정 - main.tsx 개선
• CORS 설정 추가 - vite.config.ts 수정
• Sentry 에러 모니터링 통합

추가된 컴포넌트:
• AuthGuard: 인증 보호 컴포넌트
• SignIn/SignUp: Clerk 기반 인증 UI
• ClerkProvider: Clerk 설정 래퍼
• EnvTest: 개발환경 디버깅 도구

데이터베이스:
• user_profiles, transactions, budgets, category_budgets 테이블
• Clerk JWT 토큰 기반 RLS 정책
• 자동 사용자 프로필 생성 및 동기화

Task Master:
• Task 11.1, 11.2, 11.4 완료
• 프로젝트 관리 시스템 업데이트

Note: ESLint 정리는 별도 커밋에서 진행 예정

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-13 14:01:27 +09:00

72 lines
2.1 KiB
TypeScript

import React, { useEffect } from "react";
import { useAuth, useUser } from "@clerk/clerk-react";
import { Navigate } from "react-router-dom";
import { useSupabaseWithClerk, ensureUserProfile } from "@/lib/supabase/auth";
import { Loader2 } from "lucide-react";
interface AuthGuardProps {
children: React.ReactNode;
redirectTo?: string;
}
export function AuthGuard({
children,
redirectTo = "/sign-in",
}: AuthGuardProps) {
const { isLoaded, isSignedIn } = useAuth();
const { user } = useUser();
const { getAuthenticatedSupabase } = useSupabaseWithClerk();
const [isProfileReady, setIsProfileReady] = React.useState(false);
useEffect(() => {
async function setupUserProfile() {
if (!user) return;
try {
const { supabase } = await getAuthenticatedSupabase();
// Supabase에 사용자 프로필 생성/업데이트
await ensureUserProfile(supabase, user.id, {
email: user.emailAddresses[0]?.emailAddress || "",
username: user.username || undefined,
firstName: user.firstName || undefined,
lastName: user.lastName || undefined,
profileImageUrl: user.imageUrl || undefined,
});
setIsProfileReady(true);
} catch (error) {
console.error("Error setting up user profile:", error);
// 에러가 발생해도 일단 진행하도록 함
setIsProfileReady(true);
}
}
if (isSignedIn && user) {
setupUserProfile();
} else if (!isSignedIn && isLoaded) {
setIsProfileReady(true);
}
}, [isSignedIn, user, getAuthenticatedSupabase, isLoaded]);
// Clerk가 아직 로드되지 않았을 때
if (!isLoaded || (isSignedIn && !isProfileReady)) {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
<p className="text-sm text-muted-foreground">Loading...</p>
</div>
</div>
);
}
// 로그인되지 않았을 때
if (!isSignedIn) {
return <Navigate to={redirectTo} replace />;
}
// 로그인되었을 때
return <>{children}</>;
}