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>
This commit is contained in:
hansoo
2025-07-13 14:01:27 +09:00
parent e72f9e8d26
commit c231d5be65
59 changed files with 5974 additions and 751 deletions

View File

@@ -1,20 +1,37 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, Suspense, lazy } from "react";
import { logger } from "@/utils/logger";
import NavBar from "@/components/NavBar";
import ExpenseChart from "@/components/ExpenseChart";
import AddTransactionButton from "@/components/AddTransactionButton";
import { useBudget } from "@/stores";
import { MONTHS_KR } from "@/hooks/useTransactions";
import { useIsMobile } from "@/hooks/use-mobile";
import { getCategoryColor } from "@/utils/categoryColorUtils";
import { MonthlyData } from "@/types";
// 새로 분리한 컴포넌트들 불러오기
import PeriodSelector from "@/components/analytics/PeriodSelector";
import SummaryCards from "@/components/analytics/SummaryCards";
import MonthlyComparisonChart from "@/components/analytics/MonthlyComparisonChart";
import CategorySpendingList from "@/components/analytics/CategorySpendingList";
import PaymentMethodChart from "@/components/analytics/PaymentMethodChart";
// 차트 관련 컴포넌트들을 동적 import로 변경
const ExpenseChart = lazy(() => import("@/components/ExpenseChart"));
const PeriodSelector = lazy(
() => import("@/components/analytics/PeriodSelector")
);
const SummaryCards = lazy(() => import("@/components/analytics/SummaryCards"));
const MonthlyComparisonChart = lazy(
() => import("@/components/analytics/MonthlyComparisonChart")
);
const CategorySpendingList = lazy(
() => import("@/components/analytics/CategorySpendingList")
);
const PaymentMethodChart = lazy(
() => import("@/components/analytics/PaymentMethodChart")
);
const AddTransactionButton = lazy(
() => import("@/components/AddTransactionButton")
);
// 로딩 스피너 컴포넌트
const ChartLoadingSpinner = () => (
<div className="h-48 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
);
const Analytics = () => {
const [_selectedPeriod, _setSelectedPeriod] = useState("이번 달");
@@ -143,27 +160,33 @@ const Analytics = () => {
<h1 className="font-bold neuro-text mb-3 text-xl"> </h1>
{/* Period Selector */}
<PeriodSelector
selectedPeriod={_selectedPeriod}
onPrevPeriod={handlePrevPeriod}
onNextPeriod={handleNextPeriod}
/>
<Suspense fallback={<ChartLoadingSpinner />}>
<PeriodSelector
selectedPeriod={_selectedPeriod}
onPrevPeriod={handlePrevPeriod}
onNextPeriod={handleNextPeriod}
/>
</Suspense>
{/* Summary Cards */}
<SummaryCards
totalBudget={totalBudget}
totalExpense={totalExpense}
savingsPercentage={savingsPercentage}
/>
<Suspense fallback={<ChartLoadingSpinner />}>
<SummaryCards
totalBudget={totalBudget}
totalExpense={totalExpense}
savingsPercentage={savingsPercentage}
/>
</Suspense>
</header>
{/* Monthly Comparison Chart */}
<div className="mb-8 w-full">
<h2 className="text-lg font-semibold mb-3"> </h2>
<MonthlyComparisonChart
monthlyData={monthlyData}
isEmpty={totalBudget === 0 && totalExpense === 0}
/>
<Suspense fallback={<ChartLoadingSpinner />}>
<MonthlyComparisonChart
monthlyData={monthlyData}
isEmpty={totalBudget === 0 && totalExpense === 0}
/>
</Suspense>
</div>
{/* 카테고리 비율과 지출을 하나의 카드로 합침 */}
@@ -173,14 +196,18 @@ const Analytics = () => {
{expenseData.some((item) => item.value > 0) ? (
<>
<div className="h-72 flex items-center justify-center">
<ExpenseChart data={expenseData} />
<Suspense fallback={<ChartLoadingSpinner />}>
<ExpenseChart data={expenseData} />
</Suspense>
</div>
{/* 원그래프 아래에 카테고리 지출 목록 추가 */}
<CategorySpendingList
categories={categorySpending}
totalExpense={totalExpense}
showCard={false} // 카드 감싸지 않도록 설정
/>
<Suspense fallback={<ChartLoadingSpinner />}>
<CategorySpendingList
categories={categorySpending}
totalExpense={totalExpense}
showCard={false} // 카드 감싸지 않도록 설정
/>
</Suspense>
</>
) : (
<div className="h-52 w-full flex items-center justify-center text-gray-400">
@@ -192,16 +219,24 @@ const Analytics = () => {
{/* 결제 방법 차트 추가 */}
<h2 className="text-lg font-semibold mb-3"> </h2>
<PaymentMethodChart
data={paymentMethodData}
isEmpty={!hasPaymentData}
/>
<Suspense fallback={<ChartLoadingSpinner />}>
<PaymentMethodChart
data={paymentMethodData}
isEmpty={!hasPaymentData}
/>
</Suspense>
{/* 결제 방법 차트 아래 80px 여유 공간 추가 */}
<div className="h-20"></div>
</div>
<AddTransactionButton />
<Suspense
fallback={
<div className="fixed bottom-20 right-4 w-14 h-14 rounded-full bg-gray-200 animate-pulse"></div>
}
>
<AddTransactionButton />
</Suspense>
<NavBar />
</div>
);