fix:google login
This commit is contained in:
41
src/App.jsx
41
src/App.jsx
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
14
src/contexts/RootStore.jsx
Normal file
14
src/contexts/RootStore.jsx
Normal 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;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user