280 lines
7.4 KiB
TypeScript
280 lines
7.4 KiB
TypeScript
'use client';
|
||
|
||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||
import { useRouter } from 'next/navigation';
|
||
import { Session, User } from '@supabase/supabase-js';
|
||
import supabase from './supabase';
|
||
|
||
// 定义用户类型
|
||
export type AuthUser = User | null;
|
||
|
||
// 定义验证上下文类型
|
||
export type AuthContextType = {
|
||
user: AuthUser;
|
||
session: Session | null;
|
||
isLoading: boolean;
|
||
signIn: (email: string, password: string) => Promise<{ error?: unknown }>;
|
||
signInWithGoogle: () => Promise<{ error?: unknown }>;
|
||
signInWithGitHub: () => Promise<{ error?: unknown }>;
|
||
signUp: (email: string, password: string) => Promise<void>;
|
||
signOut: () => Promise<void>;
|
||
};
|
||
|
||
// 创建验证上下文
|
||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||
|
||
// 验证提供者组件
|
||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||
const [user, setUser] = useState<AuthUser>(null);
|
||
const [session, setSession] = useState<Session | null>(null);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const router = useRouter();
|
||
|
||
// 初始化验证状态
|
||
useEffect(() => {
|
||
const getSession = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 尝试从Supabase获取会话
|
||
const { data: { session }, error } = await supabase.auth.getSession();
|
||
|
||
if (error) {
|
||
console.error('Error getting session:', error);
|
||
return;
|
||
}
|
||
|
||
// 打印会话信息,帮助调试
|
||
console.log('Supabase session loaded:', session ? 'Found' : 'Not found');
|
||
if (session) {
|
||
console.log('User authenticated:', session.user.email);
|
||
if (session.expires_at) {
|
||
console.log('Session expires at:', new Date(session.expires_at * 1000).toLocaleString());
|
||
}
|
||
}
|
||
|
||
setSession(session);
|
||
setUser(session?.user || null);
|
||
} catch (error) {
|
||
console.error('Unexpected error during getSession:', error);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
getSession();
|
||
|
||
// 监听验证状态变化
|
||
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
|
||
console.log('Auth state changed, event:', _event);
|
||
console.log('New session:', session ? 'Valid' : 'None');
|
||
setSession(session);
|
||
setUser(session?.user || null);
|
||
});
|
||
|
||
// 清理函数
|
||
return () => {
|
||
subscription.unsubscribe();
|
||
};
|
||
}, []);
|
||
|
||
// 登录函数
|
||
const signIn = async (email: string, password: string) => {
|
||
setIsLoading(true);
|
||
try {
|
||
console.log('尝试登录:', { email });
|
||
|
||
// 尝试通过Supabase登录
|
||
const { data, error } = await supabase.auth.signInWithPassword({
|
||
email,
|
||
password,
|
||
});
|
||
|
||
if (error) {
|
||
console.error('登录出错:', error);
|
||
return { error };
|
||
}
|
||
|
||
// 登录成功,设置会话和用户信息
|
||
console.log('登录成功,用户信息:', data.user?.email);
|
||
setSession(data.session);
|
||
setUser(data.user);
|
||
|
||
// 使用硬重定向代替router.push,确保页面完全刷新
|
||
console.log('准备重定向到分析页面');
|
||
|
||
// 添加短暂延时确保状态已更新,然后重定向
|
||
setTimeout(() => {
|
||
window.location.href = '/analytics';
|
||
}, 100);
|
||
|
||
return {};
|
||
} catch (error) {
|
||
console.error('登录过程出错:', error);
|
||
return { error };
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
// Google登录函数
|
||
const signInWithGoogle = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 尝试通过Supabase登录Google
|
||
const { error } = await supabase.auth.signInWithOAuth({
|
||
provider: 'google',
|
||
options: {
|
||
redirectTo: `${window.location.origin}/auth/callback`,
|
||
queryParams: {
|
||
access_type: 'offline',
|
||
prompt: 'consent',
|
||
}
|
||
},
|
||
});
|
||
|
||
if (error) {
|
||
console.error('Google登录出错:', error);
|
||
return { error };
|
||
}
|
||
|
||
return {}; // Return empty object when successful
|
||
} catch (error) {
|
||
console.error('Google登录过程出错:', error);
|
||
return { error };
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
// GitHub登录函数
|
||
const signInWithGitHub = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 尝试通过Supabase登录GitHub
|
||
const { error } = await supabase.auth.signInWithOAuth({
|
||
provider: 'github',
|
||
options: {
|
||
redirectTo: `${window.location.origin}/auth/callback`,
|
||
},
|
||
});
|
||
|
||
if (error) {
|
||
console.error('GitHub login error:', error);
|
||
return { error };
|
||
}
|
||
|
||
return {}; // Return empty object when successful
|
||
} catch (error) {
|
||
console.error('GitHub login process error:', error);
|
||
return { error };
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
// 注册函数
|
||
const signUp = async (email: string, password: string) => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 尝试通过Supabase注册
|
||
const { error } = await supabase.auth.signUp({
|
||
email,
|
||
password,
|
||
options: {
|
||
emailRedirectTo: `${window.location.origin}/auth/callback`,
|
||
}
|
||
});
|
||
|
||
if (error) {
|
||
console.error('注册出错:', error);
|
||
throw error;
|
||
}
|
||
|
||
// 注册成功后跳转到登录页面并显示确认消息
|
||
router.push('/login?message=Registration successful! Please check your email to verify your account before logging in.');
|
||
} catch (error) {
|
||
console.error('注册过程出错:', error);
|
||
throw error;
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
// 登出函数
|
||
const signOut = async () => {
|
||
setIsLoading(true);
|
||
try {
|
||
// 尝试通过Supabase登出
|
||
const { error } = await supabase.auth.signOut();
|
||
if (error) {
|
||
console.error('登出出错:', error);
|
||
throw error;
|
||
}
|
||
setSession(null);
|
||
setUser(null);
|
||
router.push('/login');
|
||
} catch (error) {
|
||
console.error('登出过程出错:', error);
|
||
throw error;
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const contextValue: AuthContextType = {
|
||
user,
|
||
session,
|
||
isLoading,
|
||
signIn,
|
||
signInWithGoogle,
|
||
signInWithGitHub,
|
||
signUp,
|
||
signOut,
|
||
};
|
||
|
||
return (
|
||
<AuthContext.Provider value={contextValue}>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
);
|
||
};
|
||
|
||
// 自定义钩子
|
||
export const useAuth = () => {
|
||
const context = useContext(AuthContext);
|
||
if (context === undefined) {
|
||
throw new Error('useAuth must be used within an AuthProvider');
|
||
}
|
||
return context;
|
||
};
|
||
|
||
// 受保护路由组件
|
||
export const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||
const { user, isLoading } = useAuth();
|
||
const router = useRouter();
|
||
|
||
useEffect(() => {
|
||
if (!isLoading && !user) {
|
||
router.push('/login');
|
||
}
|
||
}, [user, isLoading, router]);
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="flex items-center justify-center min-h-screen">
|
||
<div className="text-center">
|
||
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto"></div>
|
||
<p className="mt-4 text-lg text-gray-700 dark:text-gray-300">加载中...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!user) {
|
||
return null;
|
||
}
|
||
|
||
return <>{children}</>;
|
||
};
|
||
|
||
export default AuthContext;
|