fix:google login

This commit is contained in:
xuqssq
2024-12-21 20:52:48 +08:00
parent 3812c9b7e9
commit 36db1f29b0
6 changed files with 4421 additions and 70 deletions

View File

@@ -1,10 +1,11 @@
import React from 'react'; import React from "react";
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from "react-router-dom";
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme } from "antd";
import { ThemeProvider } from './contexts/ThemeContext'; import { ThemeProvider } from "./contexts/ThemeContext";
import { AuthProvider } from './contexts/AuthContext'; import { AuthProvider } from "./contexts/AuthContext";
import AppRoutes from './routes/AppRoutes'; import AppRoutes from "./routes/AppRoutes";
import { useTheme } from './contexts/ThemeContext'; import { useTheme } from "./contexts/ThemeContext";
import { RootStoreProvider } from "./contexts/RootStore";
const ThemedApp = () => { const ThemedApp = () => {
const { isDarkMode } = useTheme(); const { isDarkMode } = useTheme();
@@ -14,16 +15,20 @@ const ThemedApp = () => {
theme={{ theme={{
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm, algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
token: { token: {
colorPrimary: '#1677ff', colorPrimary: "#1677ff",
borderRadius: 4, borderRadius: 4,
colorBgContainer: isDarkMode ? '#141414' : '#ffffff', colorBgContainer: isDarkMode ? "#141414" : "#ffffff",
colorBgElevated: isDarkMode ? '#1f1f1f' : '#ffffff', colorBgElevated: isDarkMode ? "#1f1f1f" : "#ffffff",
colorText: isDarkMode ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0, 0, 0, 0.85)', colorText: isDarkMode
colorTextSecondary: isDarkMode ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.45)', ? "rgba(255, 255, 255, 0.85)"
: "rgba(0, 0, 0, 0.85)",
colorTextSecondary: isDarkMode
? "rgba(255, 255, 255, 0.45)"
: "rgba(0, 0, 0, 0.45)",
}, },
}} }}
> >
<div className={isDarkMode ? 'dark' : ''}> <div className={isDarkMode ? "dark" : ""}>
<AppRoutes /> <AppRoutes />
</div> </div>
</ConfigProvider> </ConfigProvider>
@@ -34,12 +39,14 @@ const App = () => {
return ( return (
<BrowserRouter> <BrowserRouter>
<AuthProvider> <AuthProvider>
<ThemeProvider> <RootStoreProvider>
<ThemedApp /> <ThemeProvider>
</ThemeProvider> <ThemedApp />
</ThemeProvider>
</RootStoreProvider>
</AuthProvider> </AuthProvider>
</BrowserRouter> </BrowserRouter>
); );
}; };
export default App; export default App;

View File

@@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { Navigate } from 'react-router-dom'; import { Navigate,useLocation } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import { Spin } from 'antd'; import { Spin } from 'antd';
export const ProtectedRoute = ({ children }) => { export const ProtectedRoute = ({ children }) => {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
const location = useLocation();
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center"> <div className="min-h-screen flex items-center justify-center">
@@ -15,7 +15,7 @@ export const ProtectedRoute = ({ children }) => {
} }
if (!user) { if (!user) {
return <Navigate to="/login" replace />; return <Navigate to={`/login`} replace />;
} }
return children; return children;

View File

@@ -1,24 +1,45 @@
import React, { createContext, useContext, useState, useEffect } from 'react'; import React, { createContext, useContext, useState, useEffect } from "react";
import { supabase } from '@/config/supabase'; import { supabase } from "@/config/supabase";
import { message } from 'antd'; import { message } from "antd";
import { useNavigate } from 'react-router-dom'; import { useNavigate, useSearchParams, useLocation } from "react-router-dom";
const AuthContext = createContext(); const AuthContext = createContext({});
export const AuthProvider = ({ children }) => { export const AuthProvider = ({ children }) => {
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => {
//处理google登录
const hash = window.location.hash.substring(1);
const hashParams = new URLSearchParams(hash);
const accessToken = hashParams.get("access_token");
const refreshToken = hashParams.get("refresh_token");
if (accessToken && refreshToken) {
(async () => {
const { data, error } = await supabase.auth.setSession({
access_token: accessToken,
refresh_token: refreshToken,
});
})();
}
}, [navigate]);
// 监听认证状态变化 // 监听认证状态变化
useEffect(() => { useEffect(() => {
// 获取初始会话状态 // 获取初始会话状态
const initSession = async () => { const initSession = async () => {
try { try {
const { data: { session } } = await supabase.auth.getSession(); const {
data: { session },
error,
} = await supabase.auth.getSession();
setUser(session?.user ?? null); setUser(session?.user ?? null);
} catch (error) { } catch (error) {
console.error('Error getting session:', error); console.error("Error getting session:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -27,7 +48,9 @@ export const AuthProvider = ({ children }) => {
initSession(); initSession();
// 订阅认证状态变化 // 订阅认证状态变化
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null); setUser(session?.user ?? null);
}); });
@@ -36,6 +59,13 @@ export const AuthProvider = ({ children }) => {
}; };
}, []); }, []);
// useEffect(() => {
// const redirectTo = searchParams.get("redirectTo");
// if (redirectTo) {
// navigate(redirectTo);
// }
// }, [location.pathname]);
// 邮箱密码登录 // 邮箱密码登录
const login = async (email, password) => { const login = async (email, password) => {
try { try {
@@ -46,14 +76,14 @@ export const AuthProvider = ({ children }) => {
}); });
if (error) { if (error) {
throw error; message.error(error.message || "登录失败,请稍后重试");
return;
} }
setUser(data.user); setUser(data.user);
return data; return data;
} catch (error) { } catch (error) {
message.error(error.message || '登录失败'); message.error(error.message || "登录失败,请稍后重试");
throw error;
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -63,21 +93,25 @@ export const AuthProvider = ({ children }) => {
const signInWithGoogle = async () => { const signInWithGoogle = async () => {
try { try {
setLoading(true); setLoading(true);
const redirectTo = searchParams.get("redirectTo");
const { data, error } = await supabase.auth.signInWithOAuth({ const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google', provider: "google",
options: { options: {
redirectTo: `${window.location.origin}/auth/callback`, redirectTo: `${window.location.origin}/login?redirectTo=${
redirectTo ?? ""
}`,
}, },
}); });
console.log(data, error, "data");
if (error) { if (error) {
throw error; message.error(error.message || "Google 登录失败,请稍后重试");
return;
} }
return data; return data;
} catch (error) { } catch (error) {
message.error(error.message || 'Google 登录失败'); message.error(error.message || "Google 登录失败,请稍后重试");
throw error;
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -98,10 +132,10 @@ export const AuthProvider = ({ children }) => {
if (error) { if (error) {
throw error; throw error;
} }
message.success('注册成功!请查收验证邮件。'); message.success("注册成功!请查收验证邮件。");
return data; return data;
} catch (error) { } catch (error) {
message.error(error.message || '注册失败'); message.error(error.message || "注册失败");
throw error; throw error;
} finally { } finally {
setLoading(false); setLoading(false);
@@ -113,20 +147,17 @@ export const AuthProvider = ({ children }) => {
try { try {
setLoading(true); setLoading(true);
const { error } = await supabase.auth.signOut({ const { error } = await supabase.auth.signOut({
scope: 'local' scope: "local",
}); });
if (error) { if (error) {
throw error; message.error(error.message || "登出失败,请稍后重试");
return;
} }
setUser(null); setUser(null);
message.success('已成功登出'); message.success("已成功登出");
navigate('/login', { replace: true }); navigate(`/login?redirectTo=${location.pathname}`, { replace: true });
} catch (error) { } catch (error) {
message.error(error.message || '登出失败'); message.error(error.message || "登出失败,请稍后重试");
throw error;
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -146,8 +177,5 @@ export const AuthProvider = ({ children }) => {
export const useAuth = () => { export const useAuth = () => {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context; return context;
}; };

View File

@@ -0,0 +1,14 @@
import React, { createContext, useContext, useState, useEffect } from "react";
const RootStoreContext = createContext({});
export const RootStoreProvider = ({ children }) => {
return (
<RootStoreContext.Provider value={{}}>{children}</RootStoreContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(RootStoreContext);
return context;
};

View File

@@ -1,30 +1,30 @@
import React from 'react'; import React from "react";
import { Form, Input, Button, Divider, message } from 'antd'; import { Form, Input, Button, Divider, message } from "antd";
import { GoogleOutlined } from '@ant-design/icons'; import { GoogleOutlined } from "@ant-design/icons";
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from "react-router-dom";
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from "@/contexts/AuthContext";
const Login = () => { const Login = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { login, signInWithGoogle } = useAuth(); const { login, signInWithGoogle, loading } = useAuth();
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleLogin = async (values) => { const handleLogin = async (values) => {
try { try {
await login(values.email, values.password); await login(values.email, values.password);
message.success('登录成功!'); message.success("登录成功!");
navigate('/'); navigate("/");
} catch (error) { } catch (error) {
console.error('Login error:', error); console.error("Login error:", error);
} }
}; };
const handleGoogleLogin = async () => { const handleGoogleLogin = async () => {
try { try {
await signInWithGoogle(); await signInWithGoogle();
navigate('/'); navigate("/");
} catch (error) { } catch (error) {
console.error('Google login error:', error); console.error("Google login error:", error);
} }
}; };
@@ -49,8 +49,8 @@ const Login = () => {
<Form.Item <Form.Item
name="email" name="email"
rules={[ rules={[
{ required: true, message: '请输入邮箱!' }, { required: true, message: "请输入邮箱!" },
{ type: 'email', message: '请输入有效的邮箱地址!' } { type: "email", message: "请输入有效的邮箱地址!" },
]} ]}
> >
<Input placeholder="邮箱" /> <Input placeholder="邮箱" />
@@ -58,16 +58,17 @@ const Login = () => {
<Form.Item <Form.Item
name="password" name="password"
rules={[ rules={[{ required: true, message: "请输入密码!" }]}
{ required: true, message: '请输入密码!' }
]}
> >
<Input.Password placeholder="密码" /> <Input.Password placeholder="密码" />
</Form.Item> </Form.Item>
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<Form.Item name="remember" valuePropName="checked" noStyle> <Form.Item name="remember" valuePropName="checked" noStyle>
<Link to="/forgot-password" className="text-primary-500 hover:text-primary-600"> <Link
to="/forgot-password"
className="text-primary-500 hover:text-primary-600"
>
忘记密码 忘记密码
</Link> </Link>
</Form.Item> </Form.Item>
@@ -86,10 +87,10 @@ const Login = () => {
block block
onClick={handleGoogleLogin} onClick={handleGoogleLogin}
className="mb-6" className="mb-6"
loading={loading}
> >
使用 Google 账号登录 使用 Google 账号登录
</Button> </Button>
</Form> </Form>
</div> </div>
@@ -99,4 +100,4 @@ const Login = () => {
); );
}; };
export default Login; export default Login;

4301
yarn.lock Normal file

File diff suppressed because it is too large Load Diff