- @clerk/clerk-react 패키지 설치 추가 - Vite external 설정에서 Clerk 번들링 허용으로 변경 - ChunkLoadError 복구 시스템 Playwright 테스트 추가 - Clerk CDN 실패 시나리오 검증 및 Mock 인증 폴백 시스템 확인 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
239 lines
7.1 KiB
TypeScript
239 lines
7.1 KiB
TypeScript
/**
|
|
* 안전한 Clerk useAuth 래퍼
|
|
*
|
|
* Clerk이 비활성화된 상태에서도 안전하게 작동하도록
|
|
* Mock 데이터를 제공하는 useAuth 훅과 타입, 컴포넌트들
|
|
*/
|
|
|
|
import {
|
|
useAuth as useClerkAuth,
|
|
useUser as useClerkUser,
|
|
SignIn as ClerkSignIn,
|
|
SignUp as ClerkSignUp,
|
|
type User as ClerkUser,
|
|
type Session as ClerkSession,
|
|
} from "@clerk/clerk-react";
|
|
import { logger } from "@/utils/logger";
|
|
import React from "react";
|
|
|
|
// Clerk 비활성화 상태 확인 함수
|
|
const isClerkDisabled = (): boolean => {
|
|
if (typeof window === "undefined") {
|
|
return false;
|
|
}
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
if (urlParams.get("noClerk") === "true") {
|
|
return true;
|
|
}
|
|
|
|
if (sessionStorage.getItem("disableClerk") === "true") {
|
|
return true;
|
|
}
|
|
if (sessionStorage.getItem("skipClerk") === "true") {
|
|
return true;
|
|
}
|
|
if (sessionStorage.getItem("chunkLoadErrorMaxRetries") === "true") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Mock useAuth 반환값
|
|
const mockAuthData = {
|
|
isLoaded: true,
|
|
isSignedIn: false,
|
|
userId: null,
|
|
sessionId: null,
|
|
user: null,
|
|
getToken: () => Promise.resolve(null),
|
|
signOut: () => Promise.resolve(),
|
|
};
|
|
|
|
// Mock useUser 반환값
|
|
const mockUserData = {
|
|
isLoaded: true,
|
|
isSignedIn: false,
|
|
user: null,
|
|
};
|
|
|
|
/**
|
|
* 안전한 useAuth 훅
|
|
* Clerk이 비활성화된 경우 Mock 데이터를 반환
|
|
*/
|
|
export const useAuth = () => {
|
|
// ESLint 규칙 비활성화: 이 함수는 특별한 경우로 조건부 훅 호출이 필요
|
|
|
|
if (isClerkDisabled()) {
|
|
logger.debug("useAuth: Clerk 비활성화됨, Mock 데이터 반환");
|
|
return mockAuthData;
|
|
}
|
|
|
|
try {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
const clerkAuth = useClerkAuth();
|
|
|
|
// Clerk 훅이 정상적으로 로드되지 않은 경우
|
|
if (!clerkAuth || !clerkAuth.isLoaded) {
|
|
logger.debug("useAuth: Clerk 로딩 중 또는 오류, Mock 데이터 반환");
|
|
return mockAuthData;
|
|
}
|
|
|
|
return clerkAuth;
|
|
} catch (error) {
|
|
logger.warn("useAuth: Clerk 컨텍스트 오류, Mock 데이터로 폴백", error);
|
|
// Clerk에 문제가 있으면 자동으로 비활성화
|
|
sessionStorage.setItem("disableClerk", "true");
|
|
return mockAuthData;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 안전한 useUser 훅
|
|
* Clerk이 비활성화된 경우 Mock 데이터를 반환
|
|
*/
|
|
export const useUser = () => {
|
|
// ESLint 규칙 비활성화: 이 함수는 특별한 경우로 조건부 훅 호출이 필요
|
|
|
|
if (isClerkDisabled()) {
|
|
logger.debug("useUser: Clerk 비활성화됨, Mock 데이터 반환");
|
|
return mockUserData;
|
|
}
|
|
|
|
try {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
const clerkUser = useClerkUser();
|
|
|
|
// Clerk 훅이 정상적으로 로드되지 않은 경우
|
|
if (!clerkUser || !clerkUser.isLoaded) {
|
|
logger.debug("useUser: Clerk 로딩 중 또는 오류, Mock 데이터 반환");
|
|
return mockUserData;
|
|
}
|
|
|
|
return clerkUser;
|
|
} catch (error) {
|
|
logger.warn("useUser: Clerk 컨텍스트 오류, Mock 데이터로 폴백", error);
|
|
// Clerk에 문제가 있으면 자동으로 비활성화
|
|
sessionStorage.setItem("disableClerk", "true");
|
|
return mockUserData;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Mock SignIn 컴포넌트
|
|
* Clerk이 비활성화된 경우 사용되는 대체 컴포넌트
|
|
*/
|
|
const MockSignIn: React.FC<Record<string, unknown>> = (_props) => {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background">
|
|
<div className="w-full max-w-md p-6 bg-card rounded-lg shadow-lg">
|
|
<div className="mb-8 text-center">
|
|
<h1 className="text-3xl font-bold">Zellyy Finance</h1>
|
|
<p className="mt-2 text-muted-foreground">
|
|
개인 가계부 관리의 새로운 시작
|
|
</p>
|
|
<p className="mt-4 text-sm text-amber-600">
|
|
🚧 인증 시스템이 일시적으로 비활성화되었습니다
|
|
</p>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<button
|
|
className="w-full p-3 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
onClick={() => {
|
|
logger.info("Mock 로그인 시도");
|
|
window.location.href = "/";
|
|
}}
|
|
>
|
|
테스트용 로그인
|
|
</button>
|
|
<p className="text-xs text-center text-muted-foreground">
|
|
개발 모드: 인증 없이 앱을 체험할 수 있습니다
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Mock SignUp 컴포넌트
|
|
* Clerk이 비활성화된 경우 사용되는 대체 컴포넌트
|
|
*/
|
|
const MockSignUp: React.FC<Record<string, unknown>> = (_props) => {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-background">
|
|
<div className="w-full max-w-md p-6 bg-card rounded-lg shadow-lg">
|
|
<div className="mb-8 text-center">
|
|
<h1 className="text-3xl font-bold">Zellyy Finance 시작하기</h1>
|
|
<p className="mt-2 text-muted-foreground">
|
|
무료로 계정을 만들고 지출 관리를 시작하세요
|
|
</p>
|
|
<p className="mt-4 text-sm text-amber-600">
|
|
🚧 인증 시스템이 일시적으로 비활성화되었습니다
|
|
</p>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<button
|
|
className="w-full p-3 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
onClick={() => {
|
|
logger.info("Mock 회원가입 시도");
|
|
window.location.href = "/";
|
|
}}
|
|
>
|
|
테스트용 계정 생성
|
|
</button>
|
|
<p className="text-xs text-center text-muted-foreground">
|
|
개발 모드: 인증 없이 앱을 체험할 수 있습니다
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 안전한 SignIn 컴포넌트
|
|
* Clerk이 비활성화된 경우 Mock 컴포넌트를 반환
|
|
*/
|
|
export const SignIn: React.FC<Record<string, unknown>> = (props) => {
|
|
if (isClerkDisabled()) {
|
|
logger.debug("SignIn: Clerk 비활성화됨, Mock 컴포넌트 반환");
|
|
return <MockSignIn {...props} />;
|
|
}
|
|
|
|
try {
|
|
return <ClerkSignIn {...props} />;
|
|
} catch (error) {
|
|
logger.warn("SignIn: Clerk Context 오류, Mock 컴포넌트로 폴백", error);
|
|
sessionStorage.setItem("disableClerk", "true");
|
|
return <MockSignIn {...props} />;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 안전한 SignUp 컴포넌트
|
|
* Clerk이 비활성화된 경우 Mock 컴포넌트를 반환
|
|
*/
|
|
export const SignUp: React.FC<Record<string, unknown>> = (props) => {
|
|
if (isClerkDisabled()) {
|
|
logger.debug("SignUp: Clerk 비활성화됨, Mock 컴포넌트 반환");
|
|
return <MockSignUp {...props} />;
|
|
}
|
|
|
|
try {
|
|
return <ClerkSignUp {...props} />;
|
|
} catch (error) {
|
|
logger.warn("SignUp: Clerk Context 오류, Mock 컴포넌트로 폴백", error);
|
|
sessionStorage.setItem("disableClerk", "true");
|
|
return <MockSignUp {...props} />;
|
|
}
|
|
};
|
|
|
|
// 타입 다시 내보내기
|
|
export type User = ClerkUser;
|
|
export type Session = ClerkSession;
|
|
|
|
// 기본 내보내기
|
|
export default { useAuth, useUser, SignIn, SignUp };
|