login page
This commit is contained in:
80
app/(app)/AppLayoutClient.tsx
Normal file
80
app/(app)/AppLayoutClient.tsx
Normal file
@@ -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 (
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
|
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
<div className="flex items-center justify-between h-16">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link href="/" className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
|
ShortURL Analytics
|
||||||
|
</Link>
|
||||||
|
<div className="hidden md:block ml-10">
|
||||||
|
<div className="flex items-baseline space-x-4">
|
||||||
|
<Link
|
||||||
|
href="/dashboard"
|
||||||
|
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
Dashboard
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/events"
|
||||||
|
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
Events
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/analytics/geo"
|
||||||
|
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
Geographic
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/analytics/devices"
|
||||||
|
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
||||||
|
>
|
||||||
|
Devices
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
{user && (
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={handleSignOut}
|
||||||
|
className="text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
退出登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main className="py-10">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</ProtectedRoute>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import '../globals.css';
|
import '../globals.css';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import Link from 'next/link';
|
import AppLayoutClient from './AppLayoutClient';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
@@ -17,50 +17,9 @@ export default function AppLayout({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={inter.className}>
|
<div className={inter.className}>
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
<AppLayoutClient>
|
||||||
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
{children}
|
||||||
<div className="container mx-auto px-4">
|
</AppLayoutClient>
|
||||||
<div className="flex items-center justify-between h-16">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Link href="/" className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
|
||||||
ShortURL Analytics
|
|
||||||
</Link>
|
|
||||||
<div className="hidden md:block ml-10">
|
|
||||||
<div className="flex items-baseline space-x-4">
|
|
||||||
<Link
|
|
||||||
href="/dashboard"
|
|
||||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
Dashboard
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/events"
|
|
||||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
Events
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/analytics/geo"
|
|
||||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
Geographic
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
href="/analytics/devices"
|
|
||||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-2 rounded-md text-sm font-medium"
|
|
||||||
>
|
|
||||||
Devices
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<main className="py-10">
|
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import './globals.css';
|
import './globals.css';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
import { AuthProvider } from '@/lib/auth';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Link Management & Analytics',
|
title: 'ShortURL Analytics',
|
||||||
description: 'Track and analyze shortened links',
|
description: 'Track and analyze shortened links',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,7 +15,9 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
{children}
|
<AuthProvider>
|
||||||
|
{children}
|
||||||
|
</AuthProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
250
app/login/page.tsx
Normal file
250
app/login/page.tsx
Normal file
@@ -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<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState<string | null>(null);
|
||||||
|
const [serverInfo, setServerInfo] = useState<string | null>(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 (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
|
||||||
|
<div className="w-full max-w-md p-8 space-y-8 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">登录</h1>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
登录您的账户以访问分析面板
|
||||||
|
</p>
|
||||||
|
{serverInfo && (
|
||||||
|
<div className="mt-2 text-xs text-gray-500 dark:text-gray-500">
|
||||||
|
{serverInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 消息提示 */}
|
||||||
|
{message && (
|
||||||
|
<div className="p-4 mb-4 text-sm text-blue-700 bg-blue-100 dark:bg-blue-900 dark:text-blue-200 rounded-lg">
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 错误提示 */}
|
||||||
|
{error && (
|
||||||
|
<div className="p-4 mb-4 text-sm text-red-700 bg-red-100 dark:bg-red-900 dark:text-red-200 rounded-lg">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
邮箱地址
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => 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="********"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isLoading ? '登录中...' : '登录'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-gray-300 dark:border-gray-600"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-sm">
|
||||||
|
<span className="px-2 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">或</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleGoogleSignIn}
|
||||||
|
className="flex justify-center items-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="h-5 w-5 mr-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
|
||||||
|
<path
|
||||||
|
fill="#4285F4"
|
||||||
|
d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#34A853"
|
||||||
|
d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FBBC05"
|
||||||
|
d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#EA4335"
|
||||||
|
d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Google登录
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleUseTestAccount}
|
||||||
|
className="flex justify-center items-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
使用测试账号
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-6 text-center">
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
还没有账号?{' '}
|
||||||
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
|
>
|
||||||
|
立即注册
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
84
app/page.tsx
84
app/page.tsx
@@ -45,39 +45,65 @@ export default function HomePage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
<div className="max-w-4xl mx-auto">
|
<header className="bg-white dark:bg-gray-800 shadow">
|
||||||
<div className="text-center mb-12">
|
<div className="container mx-auto px-4 py-4">
|
||||||
<h1 className="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4">
|
<div className="flex items-center justify-between">
|
||||||
Welcome to ShortURL Analytics
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
||||||
</h1>
|
ShortURL Analytics
|
||||||
<p className="text-lg text-gray-600 dark:text-gray-400">
|
</h1>
|
||||||
Get detailed insights into your link performance and visitor behavior
|
<div>
|
||||||
</p>
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="inline-block bg-blue-600 text-white px-4 py-2 rounded-md text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/register"
|
||||||
|
className="ml-4 inline-block bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-4 py-2 rounded-md text-sm font-medium hover:bg-gray-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||||
|
>
|
||||||
|
注册
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="container mx-auto px-4 py-16">
|
||||||
{sections.map((section) => (
|
<div className="max-w-4xl mx-auto">
|
||||||
<Link
|
<div className="text-center mb-12">
|
||||||
key={section.title}
|
<h2 className="text-4xl font-bold text-gray-900 dark:text-gray-100 mb-4">
|
||||||
href={section.href}
|
Welcome to ShortURL Analytics
|
||||||
className="group block p-6 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200"
|
</h2>
|
||||||
>
|
<p className="text-lg text-gray-600 dark:text-gray-400">
|
||||||
<div className="flex items-center mb-4">
|
Get detailed insights into your link performance and visitor behavior
|
||||||
<div className="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg mr-4">
|
</p>
|
||||||
<div className="text-blue-600 dark:text-blue-300">
|
</div>
|
||||||
{section.icon}
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{sections.map((section) => (
|
||||||
|
<Link
|
||||||
|
key={section.title}
|
||||||
|
href={section.href}
|
||||||
|
className="group block p-6 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition-shadow duration-200"
|
||||||
|
>
|
||||||
|
<div className="flex items-center mb-4">
|
||||||
|
<div className="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg mr-4">
|
||||||
|
<div className="text-blue-600 dark:text-blue-300">
|
||||||
|
{section.icon}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200">
|
||||||
|
{section.title}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200">
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
{section.title}
|
{section.description}
|
||||||
</h2>
|
</p>
|
||||||
</div>
|
</Link>
|
||||||
<p className="text-gray-600 dark:text-gray-400">
|
))}
|
||||||
{section.description}
|
</div>
|
||||||
</p>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
195
app/register/page.tsx
Normal file
195
app/register/page.tsx
Normal file
@@ -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<string | null>(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 (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
|
||||||
|
<div className="w-full max-w-md p-8 space-y-8 bg-white dark:bg-gray-800 rounded-lg shadow-md">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">注册</h1>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
创建您的帐户以访问分析仪表板
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 错误提示 */}
|
||||||
|
{error && (
|
||||||
|
<div className="p-4 mb-4 text-sm text-red-700 bg-red-100 dark:bg-red-900 dark:text-red-200 rounded-lg">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
邮箱地址
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => 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="********"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
确认密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="confirmPassword"
|
||||||
|
name="confirmPassword"
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
required
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => 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="********"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{isLoading ? '注册中...' : '注册'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-gray-300 dark:border-gray-600"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-sm">
|
||||||
|
<span className="px-2 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">或</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleGoogleSignIn}
|
||||||
|
className="w-full flex justify-center items-center py-2 px-4 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="h-5 w-5 mr-2"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
>
|
||||||
|
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
|
||||||
|
<path
|
||||||
|
fill="#4285F4"
|
||||||
|
d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#34A853"
|
||||||
|
d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FBBC05"
|
||||||
|
d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#EA4335"
|
||||||
|
d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
使用Google账号注册
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-6 text-center">
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
已有账号?{' '}
|
||||||
|
<Link
|
||||||
|
href="/login"
|
||||||
|
className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
256
lib/auth.tsx
Normal file
256
lib/auth.tsx
Normal file
@@ -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<void>;
|
||||||
|
signInWithGoogle: () => Promise<void>;
|
||||||
|
signUp: (email: string, password: string) => Promise<void>;
|
||||||
|
signOut: () => Promise<void>;
|
||||||
|
autoRegisterTestUser: () => Promise<void>; // 添加自动注册测试用户函数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建验证上下文
|
||||||
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
// 测试账户常量 - 使用已验证的账户
|
||||||
|
const TEST_EMAIL = 'vitalitymailg@gmail.com';
|
||||||
|
const TEST_PASSWORD = 'password123';
|
||||||
|
|
||||||
|
// 验证提供者组件
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<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;
|
||||||
65
lib/supabase.ts
Normal file
65
lib/supabase.ts
Normal file
@@ -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;
|
||||||
@@ -37,11 +37,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.2.3",
|
"eslint-config-next": "15.2.3",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^4.0.0",
|
||||||
|
|||||||
120
pnpm-lock.yaml
generated
120
pnpm-lock.yaml
generated
@@ -45,6 +45,9 @@ importers:
|
|||||||
'@eslint/eslintrc':
|
'@eslint/eslintrc':
|
||||||
specifier: ^3
|
specifier: ^3
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
|
'@supabase/supabase-js':
|
||||||
|
specifier: ^2.49.4
|
||||||
|
version: 2.49.4
|
||||||
'@tailwindcss/postcss':
|
'@tailwindcss/postcss':
|
||||||
specifier: ^4
|
specifier: ^4
|
||||||
version: 4.0.15
|
version: 4.0.15
|
||||||
@@ -60,6 +63,9 @@ importers:
|
|||||||
css-loader:
|
css-loader:
|
||||||
specifier: ^7.1.2
|
specifier: ^7.1.2
|
||||||
version: 7.1.2(webpack@5.98.0)
|
version: 7.1.2(webpack@5.98.0)
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.4.7
|
||||||
|
version: 16.4.7
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9
|
specifier: ^9
|
||||||
version: 9.22.0(jiti@2.4.2)
|
version: 9.22.0(jiti@2.4.2)
|
||||||
@@ -368,6 +374,28 @@ packages:
|
|||||||
'@rushstack/eslint-patch@1.11.0':
|
'@rushstack/eslint-patch@1.11.0':
|
||||||
resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
|
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':
|
'@swc/counter@0.1.3':
|
||||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
|
|
||||||
@@ -507,6 +535,9 @@ packages:
|
|||||||
'@types/node@20.17.24':
|
'@types/node@20.17.24':
|
||||||
resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==}
|
resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==}
|
||||||
|
|
||||||
|
'@types/phoenix@1.6.6':
|
||||||
|
resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==}
|
||||||
|
|
||||||
'@types/react-dom@19.0.4':
|
'@types/react-dom@19.0.4':
|
||||||
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -521,6 +552,9 @@ packages:
|
|||||||
'@types/uuid@10.0.0':
|
'@types/uuid@10.0.0':
|
||||||
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
|
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':
|
'@typescript-eslint/eslint-plugin@8.27.0':
|
||||||
resolution: {integrity: sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==}
|
resolution: {integrity: sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -976,6 +1010,10 @@ packages:
|
|||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||||
|
|
||||||
|
dotenv@16.4.7:
|
||||||
|
resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2057,6 +2095,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
engines: {node: '>=8.0'}
|
engines: {node: '>=8.0'}
|
||||||
|
|
||||||
|
tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
ts-api-utils@2.1.0:
|
ts-api-utils@2.1.0:
|
||||||
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
|
||||||
engines: {node: '>=18.12'}
|
engines: {node: '>=18.12'}
|
||||||
@@ -2124,6 +2165,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
|
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1:
|
||||||
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
|
||||||
webpack-sources@3.2.3:
|
webpack-sources@3.2.3:
|
||||||
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -2138,6 +2182,9 @@ packages:
|
|||||||
webpack-cli:
|
webpack-cli:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
|
||||||
which-boxed-primitive@1.1.1:
|
which-boxed-primitive@1.1.1:
|
||||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2163,6 +2210,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
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:
|
yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2408,6 +2467,48 @@ snapshots:
|
|||||||
|
|
||||||
'@rushstack/eslint-patch@1.11.0': {}
|
'@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/counter@0.1.3': {}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
@@ -2535,6 +2636,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
|
|
||||||
|
'@types/phoenix@1.6.6': {}
|
||||||
|
|
||||||
'@types/react-dom@19.0.4(@types/react@19.0.12)':
|
'@types/react-dom@19.0.4(@types/react@19.0.12)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.0.12
|
'@types/react': 19.0.12
|
||||||
@@ -2550,6 +2653,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/uuid@10.0.0': {}
|
'@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)':
|
'@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:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.1
|
||||||
@@ -3060,6 +3167,8 @@ snapshots:
|
|||||||
'@babel/runtime': 7.27.0
|
'@babel/runtime': 7.27.0
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
|
dotenv@16.4.7: {}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@@ -4337,6 +4446,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number: 7.0.0
|
is-number: 7.0.0
|
||||||
|
|
||||||
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
ts-api-utils@2.1.0(typescript@5.8.2):
|
ts-api-utils@2.1.0(typescript@5.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.8.2
|
typescript: 5.8.2
|
||||||
@@ -4434,6 +4545,8 @@ snapshots:
|
|||||||
glob-to-regexp: 0.4.1
|
glob-to-regexp: 0.4.1
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
|
|
||||||
|
webidl-conversions@3.0.1: {}
|
||||||
|
|
||||||
webpack-sources@3.2.3: {}
|
webpack-sources@3.2.3: {}
|
||||||
|
|
||||||
webpack@5.98.0:
|
webpack@5.98.0:
|
||||||
@@ -4466,6 +4579,11 @@ snapshots:
|
|||||||
- esbuild
|
- esbuild
|
||||||
- uglify-js
|
- uglify-js
|
||||||
|
|
||||||
|
whatwg-url@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
tr46: 0.0.3
|
||||||
|
webidl-conversions: 3.0.1
|
||||||
|
|
||||||
which-boxed-primitive@1.1.1:
|
which-boxed-primitive@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-bigint: 1.1.0
|
is-bigint: 1.1.0
|
||||||
@@ -4513,4 +4631,6 @@ snapshots:
|
|||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
|
ws@8.18.1: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|||||||
72
test-supabase-login.mjs
Normal file
72
test-supabase-login.mjs
Normal file
@@ -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();
|
||||||
Reference in New Issue
Block a user