diff --git a/app/(app)/AppLayoutClient.tsx b/app/(app)/AppLayoutClient.tsx new file mode 100644 index 0000000..6686067 --- /dev/null +++ b/app/(app)/AppLayoutClient.tsx @@ -0,0 +1,80 @@ +'use client'; + +import Link from 'next/link'; +import { ProtectedRoute, useAuth } from '@/lib/auth'; + +export default function AppLayoutClient({ + children, +}: { + children: React.ReactNode; +}) { + const { signOut, user } = useAuth(); + + const handleSignOut = async () => { + await signOut(); + }; + + return ( + +
+ +
+ {children} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index 6dea6a1..9f533c0 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -1,7 +1,7 @@ import '../globals.css'; import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; -import Link from 'next/link'; +import AppLayoutClient from './AppLayoutClient'; const inter = Inter({ subsets: ['latin'] }); @@ -17,50 +17,9 @@ export default function AppLayout({ }) { return (
-
- -
- {children} -
-
+ + {children} +
); } \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 83deb4c..69c9bc0 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,8 +1,9 @@ import './globals.css'; import type { Metadata } from 'next'; +import { AuthProvider } from '@/lib/auth'; export const metadata: Metadata = { - title: 'Link Management & Analytics', + title: 'ShortURL Analytics', description: 'Track and analyze shortened links', }; @@ -14,7 +15,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..1713c2a --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,250 @@ +'use client'; + +import { useState, FormEvent, useEffect } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import { useAuth } from '@/lib/auth'; +import supabase from '@/lib/supabase'; + +export default function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState(null); + const [serverInfo, setServerInfo] = useState(null); + const router = useRouter(); + const searchParams = useSearchParams(); + const { signIn, signInWithGoogle, user, autoRegisterTestUser } = useAuth(); + + // 从URL参数中获取消息 + useEffect(() => { + const message = searchParams.get('message'); + if (message) { + setMessage(message); + } + + // 如果用户已登录,跳转到dashboard + if (user) { + router.push('/dashboard'); + } + + // 检查服务器状态 + const checkServerStatus = async () => { + try { + const { data, error } = await supabase.auth.getSession(); + if (error) { + console.log('Session check error:', error); + setServerInfo(`服务器连接状态: 错误 (${error.message})`); + } else { + console.log('Session check success:', data); + setServerInfo('服务器连接状态: 正常'); + } + } catch (error) { + console.error('Server check error:', error); + setServerInfo('服务器连接状态: 无法连接'); + } + }; + + checkServerStatus(); + }, [searchParams, user, router]); + + // 处理登录表单提交 + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(null); + setIsLoading(true); + + try { + console.log('Attempting to sign in with:', { email, password }); + await signIn(email, password); + // 登录成功后会自动跳转到dashboard + } catch (error: unknown) { + console.error('Login error:', error); + // 显示更详细的错误信息 + if (error instanceof Error) { + setError(`登录失败: ${error.message}`); + } else { + setError('登录失败,请检查邮箱和密码是否正确'); + } + } finally { + setIsLoading(false); + } + }; + + // 处理Google登录 + const handleGoogleSignIn = async () => { + setError(null); + try { + await signInWithGoogle(); + // 登录流程会重定向到Google,然后回到应用 + } catch (error: unknown) { + console.error('Google sign in error:', error); + if (error instanceof Error) { + setError(`Google登录失败: ${error.message}`); + } else { + setError('Google登录失败,请稍后再试'); + } + } + }; + + // 使用测试账户 + const handleUseTestAccount = async () => { + setError(null); + setIsLoading(true); + + try { + // 使用AuthContext中的自动注册测试账户功能 + await autoRegisterTestUser(); + } catch (error) { + console.error('测试账户处理错误:', error); + setError(error instanceof Error ? error.message : '测试账户创建失败'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+

登录

+

+ 登录您的账户以访问分析面板 +

+ {serverInfo && ( +
+ {serverInfo} +
+ )} +
+ + {/* 消息提示 */} + {message && ( +
+ {message} +
+ )} + + {/* 错误提示 */} + {error && ( +
+ {error} +
+ )} + +
+
+
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500" + placeholder="your@email.com" + /> +
+
+ + setPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500" + placeholder="********" + /> +
+
+ +
+ +
+ +
+
+
+
+
+ +
+
+ +
+ + + +
+
+ +
+

+ 还没有账号?{' '} + + 立即注册 + +

+
+
+
+ ); +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 4128c67..7f0c850 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -45,39 +45,65 @@ export default function HomePage() { ]; return ( -
-
-
-

- Welcome to ShortURL Analytics -

-

- Get detailed insights into your link performance and visitor behavior -

+
+
+
+
+

+ ShortURL Analytics +

+
+ + 登录 + + + 注册 + +
+
+
-
- {sections.map((section) => ( - -
-
-
- {section.icon} +
+
+
+

+ Welcome to ShortURL Analytics +

+

+ Get detailed insights into your link performance and visitor behavior +

+
+ +
+ {sections.map((section) => ( + +
+
+
+ {section.icon} +
+

+ {section.title} +

-

- {section.title} -

-
-

- {section.description} -

- - ))} +

+ {section.description} +

+ + ))} +
diff --git a/app/register/page.tsx b/app/register/page.tsx new file mode 100644 index 0000000..42060d3 --- /dev/null +++ b/app/register/page.tsx @@ -0,0 +1,195 @@ +'use client'; + +import { useState, FormEvent } from 'react'; +import Link from 'next/link'; +import { useAuth } from '@/lib/auth'; + +export default function RegisterPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const { signUp, signInWithGoogle } = useAuth(); + + // 处理注册表单提交 + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(null); + + // 验证密码 + if (password !== confirmPassword) { + setError('两次输入的密码不一致'); + return; + } + + // 密码强度验证 + if (password.length < 6) { + setError('密码长度至少为6个字符'); + return; + } + + setIsLoading(true); + try { + await signUp(email, password); + // 注册成功后会跳转到登录页面,提示用户验证邮箱 + } catch (error) { + console.error('Registration error:', error); + setError('注册失败,请稍后再试或使用其他邮箱'); + } finally { + setIsLoading(false); + } + }; + + // 处理Google注册/登录 + const handleGoogleSignIn = async () => { + setError(null); + try { + await signInWithGoogle(); + // 登录流程会重定向到Google,然后回到应用 + } catch (error) { + console.error('Google sign in error:', error); + setError('Google登录失败,请稍后再试'); + } + }; + + return ( +
+
+
+

注册

+

+ 创建您的帐户以访问分析仪表板 +

+
+ + {/* 错误提示 */} + {error && ( +
+ {error} +
+ )} + +
+
+
+ + setEmail(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500" + placeholder="your@email.com" + /> +
+
+ + setPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500" + placeholder="********" + /> +
+
+ + setConfirmPassword(e.target.value)} + className="mt-1 block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-blue-500 focus:border-blue-500" + placeholder="********" + /> +
+
+ +
+ +
+ +
+
+
+
+
+ +
+
+ +
+ +
+
+ +
+

+ 已有账号?{' '} + + 登录 + +

+
+
+
+ ); +} \ No newline at end of file diff --git a/lib/auth.tsx b/lib/auth.tsx new file mode 100644 index 0000000..5b38db5 --- /dev/null +++ b/lib/auth.tsx @@ -0,0 +1,256 @@ +'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; + signInWithGoogle: () => Promise; + signUp: (email: string, password: string) => Promise; + signOut: () => Promise; + autoRegisterTestUser: () => Promise; // 添加自动注册测试用户函数 +}; + +// 创建验证上下文 +const AuthContext = createContext(undefined); + +// 测试账户常量 - 使用已验证的账户 +const TEST_EMAIL = 'vitalitymailg@gmail.com'; +const TEST_PASSWORD = 'password123'; + +// 验证提供者组件 +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [session, setSession] = useState(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; + } + + 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) => { + 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); + throw error; + } + + setSession(data.session); + setUser(data.user); + router.push('/dashboard'); + } catch (error) { + console.error('登录过程出错:', error); + throw 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`, + }, + }); + + if (error) { + console.error('Google登录出错:', error); + throw error; + } + } catch (error) { + console.error('Google登录过程出错:', error); + throw error; + } finally { + setIsLoading(false); + } + }; + + // 注册函数 + const signUp = async (email: string, password: string) => { + setIsLoading(true); + try { + // 尝试通过Supabase注册 + const { error } = await supabase.auth.signUp({ + email, + password, + }); + + if (error) { + console.error('注册出错:', error); + throw error; + } + + // 注册成功后跳转到登录页面并显示确认消息 + router.push('/login?message=注册成功,请查看邮箱确认账户'); + } 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 autoRegisterTestUser = async () => { + setIsLoading(true); + try { + console.log('正在使用测试账户登录:', TEST_EMAIL); + + // 使用测试账户直接登录 + const { data, error } = await supabase.auth.signInWithPassword({ + email: TEST_EMAIL, + password: TEST_PASSWORD, + }); + + if (error) { + console.error('测试账户登录失败:', error); + throw error; + } + + console.log('测试账户登录成功!'); + setSession(data.session); + setUser(data.user); + router.push('/dashboard'); + } catch (error) { + console.error('测试账户登录出错:', error); + throw error; + } finally { + setIsLoading(false); + } + }; + + const contextValue: AuthContextType = { + user, + session, + isLoading, + signIn, + signInWithGoogle, + signUp, + signOut, + autoRegisterTestUser, + }; + + return ( + + {children} + + ); +}; + +// 自定义钩子 +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 ( +
+
+
+

加载中...

+
+
+ ); + } + + if (!user) { + return null; + } + + return <>{children}; +}; + +export default AuthContext; \ No newline at end of file diff --git a/lib/supabase.ts b/lib/supabase.ts new file mode 100644 index 0000000..139073e --- /dev/null +++ b/lib/supabase.ts @@ -0,0 +1,65 @@ +import { createClient } from '@supabase/supabase-js'; + +// 从环境变量获取Supabase配置 +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || ''; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || ''; + +console.log('Supabase Configuration Check:', { + urlDefined: !!supabaseUrl, + keyDefined: !!supabaseAnonKey, + url: supabaseUrl, + // 打印部分key以便调试 + keyPrefix: supabaseAnonKey ? supabaseAnonKey.substring(0, 20) + '...' : 'undefined', + keyLength: supabaseAnonKey ? supabaseAnonKey.length : 0 +}); + +if (!supabaseUrl || !supabaseAnonKey) { + console.error('Supabase URL and Anon Key are required'); +} + +// 尝试解码JWT token并打印解码内容 +try { + if (supabaseAnonKey) { + const parts = supabaseAnonKey.split('.'); + if (parts.length === 3) { + const payload = parts[1]; + const decoded = atob(payload); + console.log('JWT Payload:', decoded); + } else { + console.error('Invalid JWT format, expected 3 parts but got:', parts.length); + } + } +} catch (error) { + console.error('JWT解码失败:', error); +} + +// 创建Supabase客户端 +export const supabase = createClient(supabaseUrl, supabaseAnonKey, { + auth: { + persistSession: true, + autoRefreshToken: true, + detectSessionInUrl: true, + } +}); + +// 测试Supabase连接 +supabase.auth.onAuthStateChange((event, session) => { + console.log(`Supabase auth event: ${event}`, session ? 'Session exists' : 'No session'); + if (session) { + console.log('Current user:', session.user.email); + } +}); + +// 尝试执行健康检查 +async function checkSupabaseHealth() { + try { + const { data, error } = await supabase.from('_health').select('*').limit(1); + console.log('Supabase health check:', error ? `Error: ${error.message}` : 'Success', data); + } catch (error) { + console.error('Supabase health check error:', error); + } +} + +checkSupabaseHealth(); + +export default supabase; \ No newline at end of file diff --git a/package.json b/package.json index 5635180..5c70b52 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,13 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@supabase/supabase-js": "^2.49.4", "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "css-loader": "^7.1.2", + "dotenv": "^16.4.7", "eslint": "^9", "eslint-config-next": "15.2.3", "style-loader": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b613a53..8202bb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: '@eslint/eslintrc': specifier: ^3 version: 3.3.0 + '@supabase/supabase-js': + specifier: ^2.49.4 + version: 2.49.4 '@tailwindcss/postcss': specifier: ^4 version: 4.0.15 @@ -60,6 +63,9 @@ importers: css-loader: specifier: ^7.1.2 version: 7.1.2(webpack@5.98.0) + dotenv: + specifier: ^16.4.7 + version: 16.4.7 eslint: specifier: ^9 version: 9.22.0(jiti@2.4.2) @@ -368,6 +374,28 @@ packages: '@rushstack/eslint-patch@1.11.0': resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + '@supabase/auth-js@2.69.1': + resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==} + + '@supabase/functions-js@2.4.4': + resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==} + + '@supabase/node-fetch@2.6.15': + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + + '@supabase/postgrest-js@1.19.4': + resolution: {integrity: sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==} + + '@supabase/realtime-js@2.11.2': + resolution: {integrity: sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==} + + '@supabase/storage-js@2.7.1': + resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==} + + '@supabase/supabase-js@2.49.4': + resolution: {integrity: sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -507,6 +535,9 @@ packages: '@types/node@20.17.24': resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==} + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + '@types/react-dom@19.0.4': resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==} peerDependencies: @@ -521,6 +552,9 @@ packages: '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.27.0': resolution: {integrity: sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -976,6 +1010,10 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2057,6 +2095,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2124,6 +2165,9 @@ packages: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} @@ -2138,6 +2182,9 @@ packages: webpack-cli: optional: true + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2163,6 +2210,18 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2408,6 +2467,48 @@ snapshots: '@rushstack/eslint-patch@1.11.0': {} + '@supabase/auth-js@2.69.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/functions-js@2.4.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/node-fetch@2.6.15': + dependencies: + whatwg-url: 5.0.0 + + '@supabase/postgrest-js@1.19.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/realtime-js@2.11.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.7.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/supabase-js@2.49.4': + dependencies: + '@supabase/auth-js': 2.69.1 + '@supabase/functions-js': 2.4.4 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.19.4 + '@supabase/realtime-js': 2.11.2 + '@supabase/storage-js': 2.7.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -2535,6 +2636,8 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/phoenix@1.6.6': {} + '@types/react-dom@19.0.4(@types/react@19.0.12)': dependencies: '@types/react': 19.0.12 @@ -2550,6 +2653,10 @@ snapshots: '@types/uuid@10.0.0': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 20.17.24 + '@typescript-eslint/eslint-plugin@8.27.0(@typescript-eslint/parser@8.27.0(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -3060,6 +3167,8 @@ snapshots: '@babel/runtime': 7.27.0 csstype: 3.1.3 + dotenv@16.4.7: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4337,6 +4446,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-api-utils@2.1.0(typescript@5.8.2): dependencies: typescript: 5.8.2 @@ -4434,6 +4545,8 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + webidl-conversions@3.0.1: {} + webpack-sources@3.2.3: {} webpack@5.98.0: @@ -4466,6 +4579,11 @@ snapshots: - esbuild - uglify-js + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -4513,4 +4631,6 @@ snapshots: word-wrap@1.2.5: {} + ws@8.18.1: {} + yocto-queue@0.1.0: {} diff --git a/test-supabase-login.mjs b/test-supabase-login.mjs new file mode 100644 index 0000000..6502f9c --- /dev/null +++ b/test-supabase-login.mjs @@ -0,0 +1,72 @@ +// 测试Supabase登录功能 +import { config } from 'dotenv'; +import { createClient } from '@supabase/supabase-js'; + +// 加载环境变量 +config({ path: '.env.local' }); + +async function testSupabaseLogin() { + // 获取Supabase配置 + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL; + const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY; + + console.log('Supabase Configuration:'); + console.log('- URL defined:', !!supabaseUrl); + console.log('- Key defined:', !!supabaseKey); + console.log('- URL:', supabaseUrl); + + if (!supabaseUrl || !supabaseKey) { + console.error('缺少Supabase配置信息,请检查.env.local文件'); + return; + } + + // 创建Supabase客户端 + const supabase = createClient(supabaseUrl, supabaseKey); + console.log('Supabase客户端创建成功'); + + try { + // 尝试获取会话状态 + console.log('检查当前会话...'); + const { data: sessionData, error: sessionError } = await supabase.auth.getSession(); + + if (sessionError) { + console.error('获取会话失败:', sessionError.message); + } else { + console.log('会话状态:', sessionData.session ? '已登录' : '未登录'); + } + + // 尝试使用测试账户登录 + const testEmail = 'test@example.com'; + const testPassword = 'password123'; + + console.log(`\n尝试使用测试账户登录: ${testEmail}`); + const { data, error } = await supabase.auth.signInWithPassword({ + email: testEmail, + password: testPassword + }); + + if (error) { + console.error('登录失败:', error.message); + + // 如果登录失败,尝试注册账户 + console.log('\n尝试注册测试账户...'); + const { data: signUpData, error: signUpError } = await supabase.auth.signUp({ + email: testEmail, + password: testPassword + }); + + if (signUpError) { + console.error('注册失败:', signUpError.message); + } else { + console.log('注册成功:', signUpData); + } + } else { + console.log('登录成功!'); + console.log('用户信息:', data.user); + } + } catch (error) { + console.error('发生错误:', error.message); + } +} + +testSupabaseLogin(); \ No newline at end of file