Compare commits
2 Commits
ed01b88e04
...
dafa7f53ac
| Author | SHA1 | Date | |
|---|---|---|---|
| dafa7f53ac | |||
| 0203cb4041 |
50
README-google-auth.md
Normal file
50
README-google-auth.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 配置 Google 登录功能
|
||||
|
||||
为了启用 Google 登录功能,您需要在 Supabase 和 Google Cloud Platform 进行配置。
|
||||
|
||||
## 步骤 1: 创建 Google OAuth 客户端
|
||||
|
||||
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. 创建一个新项目或选择现有项目
|
||||
3. 在左侧菜单中导航到 "API 和服务" > "OAuth 同意屏幕"
|
||||
4. 选择用户类型(外部或内部),然后点击"创建"
|
||||
5. 填写必要的信息(应用名称、用户支持电子邮件等)并保存
|
||||
6. 导航到 "API 和服务" > "凭据"
|
||||
7. 点击"创建凭据" > "OAuth 客户端 ID"
|
||||
8. 应用类型选择 "Web 应用"
|
||||
9. 名称中输入您的应用名称
|
||||
10. 添加以下已获授权的重定向 URI:
|
||||
- `https://mwwvqwevplndzvmqmrxa.supabase.co/auth/v1/callback`
|
||||
11. 点击"创建"
|
||||
12. 复制生成的 "客户端 ID" 和 "客户端密钥"
|
||||
|
||||
## 步骤 2: 在 Supabase 中配置 Google 提供商
|
||||
|
||||
1. 登录 [Supabase 仪表板](https://app.supabase.com)
|
||||
2. 选择您的项目
|
||||
3. 导航到 "身份验证" > "提供商"
|
||||
4. 找到 Google 提供商并启用它
|
||||
5. 粘贴您刚才获取的 "客户端 ID" 和 "客户端密钥"
|
||||
6. 保存配置
|
||||
|
||||
## 步骤 3: 更新重定向 URL(如有需要)
|
||||
|
||||
如果您的应用需要在登录后重定向到特定页面,请确保在 Google Cloud Console 和 Supabase 中配置了正确的重定向 URL。
|
||||
|
||||
在 Supabase 中:
|
||||
1. 导航到 "身份验证" > "URL 配置"
|
||||
2. 添加您的前端 URL 到站点 URL 字段中
|
||||
3. 设置重定向 URL(通常是您的前端 URL)
|
||||
|
||||
## 测试
|
||||
|
||||
1. 在您的应用中,尝试使用 Google 登录
|
||||
2. 验证认证流程,确保可以成功登录并重定向到应用
|
||||
3. 检查 Supabase 中的用户数据,确认新用户已创建
|
||||
|
||||
## 故障排除
|
||||
|
||||
- 确保重定向 URI 完全匹配
|
||||
- 确保 OAuth 同意屏幕已正确配置
|
||||
- 查看 Supabase 和应用程序中的日志以获取详细的错误信息
|
||||
- 如果遇到 CORS 错误,检查您的站点 URL 配置
|
||||
17
app/auth/callback/route.ts
Normal file
17
app/auth/callback/route.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { cookies } from 'next/headers';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
|
||||
if (code) {
|
||||
const cookieStore = cookies();
|
||||
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
||||
await supabase.auth.exchangeCodeForSession(code);
|
||||
}
|
||||
|
||||
// URL to redirect to after sign in process completes
|
||||
return NextResponse.redirect(new URL('/analytics', request.url));
|
||||
}
|
||||
@@ -47,11 +47,11 @@ function CreateShortUrlForm() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
// 使用 useEffect 在加载时添加用户信息到表单数据中
|
||||
// Use useEffect to add user information to form data on load
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
console.log('当前用户:', user.email);
|
||||
// 可以在这里添加用户相关数据到表单中
|
||||
console.log('Current user:', user.email);
|
||||
// Can add user-related data to the form here
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
@@ -93,32 +93,32 @@ function CreateShortUrlForm() {
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 验证必填字段
|
||||
// Validate required fields
|
||||
if (!formData.originalUrl) {
|
||||
throw new Error('原始 URL 是必填项');
|
||||
throw new Error('Original URL is required');
|
||||
}
|
||||
|
||||
if (!formData.title) {
|
||||
throw new Error('标题是必填项');
|
||||
throw new Error('Title is required');
|
||||
}
|
||||
|
||||
if (!formData.teamId) {
|
||||
throw new Error('团队是必填项');
|
||||
throw new Error('Team is required');
|
||||
}
|
||||
|
||||
if (!formData.projectId) {
|
||||
throw new Error('项目是必填项');
|
||||
throw new Error('Project is required');
|
||||
}
|
||||
|
||||
if (!formData.domain) {
|
||||
throw new Error('域名是必填项');
|
||||
throw new Error('Domain is required');
|
||||
}
|
||||
|
||||
// 按照API要求构建请求数据
|
||||
// Construct request data according to API requirements
|
||||
const requestData = {
|
||||
type: "shorturl",
|
||||
attributes: {
|
||||
// 可以添加任何额外属性,但attributes不能为空
|
||||
// Can add any additional attributes, but attributes cannot be empty
|
||||
icon: ""
|
||||
},
|
||||
shortUrl: {
|
||||
@@ -134,20 +134,20 @@ function CreateShortUrlForm() {
|
||||
tagIds: formData.tags && formData.tags.length > 0 ? formData.tags : undefined
|
||||
};
|
||||
|
||||
// 调用 API 创建 shorturl 资源
|
||||
// Call API to create shorturl resource
|
||||
const response = await limqRequest('resource/shorturl', 'POST', requestData as unknown as Record<string, unknown>);
|
||||
|
||||
console.log('创建成功:', response);
|
||||
console.log('Created successfully:', response);
|
||||
setSuccess(true);
|
||||
|
||||
// 2秒后跳转到链接列表页面
|
||||
// Redirect to links list page after 2 seconds
|
||||
setTimeout(() => {
|
||||
router.push('/links');
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
console.error('创建短链接失败:', err);
|
||||
setError(err instanceof Error ? err.message : '创建短链接失败,请稍后重试');
|
||||
console.error('Failed to create short URL:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to create short URL, please try again later');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -188,7 +188,7 @@ function CreateShortUrlForm() {
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-green-700">
|
||||
短链接创建成功!正在跳转...
|
||||
Short URL created successfully! Redirecting...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,10 +196,10 @@ function CreateShortUrlForm() {
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="p-6 space-y-6">
|
||||
{/* 标题 */}
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
|
||||
标题 <span className="text-red-500">*</span>
|
||||
Title <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -207,16 +207,16 @@ function CreateShortUrlForm() {
|
||||
name="title"
|
||||
value={formData.title}
|
||||
onChange={handleChange}
|
||||
placeholder="例如:产品发布活动"
|
||||
placeholder="e.g., Product Launch Campaign"
|
||||
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 原始 URL */}
|
||||
{/* Original URL */}
|
||||
<div>
|
||||
<label htmlFor="originalUrl" className="block text-sm font-medium text-gray-700">
|
||||
原始 URL <span className="text-red-500">*</span>
|
||||
Original URL <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
@@ -230,10 +230,10 @@ function CreateShortUrlForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 自定义短链接 */}
|
||||
{/* Custom Short Link */}
|
||||
<div>
|
||||
<label htmlFor="customSlug" className="block text-sm font-medium text-gray-700">
|
||||
自定义短链接 <span className="text-gray-500">(可选)</span>
|
||||
Custom Short Link <span className="text-gray-500">(Optional)</span>
|
||||
</label>
|
||||
<div className="flex mt-1 rounded-md shadow-sm">
|
||||
<span className="inline-flex items-center px-3 py-2 text-sm text-gray-500 border border-r-0 border-gray-300 rounded-l-md bg-gray-50">
|
||||
@@ -250,14 +250,14 @@ function CreateShortUrlForm() {
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
留空将生成随机短链接
|
||||
Leave blank to generate a random short link
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 域名 */}
|
||||
{/* Domain */}
|
||||
<div>
|
||||
<label htmlFor="domain" className="block text-sm font-medium text-gray-700">
|
||||
域名 <span className="text-red-500">*</span>
|
||||
Domain <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -265,16 +265,16 @@ function CreateShortUrlForm() {
|
||||
name="domain"
|
||||
value={formData.domain}
|
||||
onChange={handleChange}
|
||||
placeholder="例如:googleads.link"
|
||||
placeholder="e.g., googleads.link"
|
||||
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 团队选择 */}
|
||||
{/* Team Selection */}
|
||||
<div>
|
||||
<label htmlFor="teamId" className="block text-sm font-medium text-gray-700">
|
||||
团队 <span className="text-red-500">*</span>
|
||||
Team <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<TeamSelector
|
||||
@@ -283,7 +283,7 @@ function CreateShortUrlForm() {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
teamId: teamId as string,
|
||||
// 当团队变更时清除已选项目
|
||||
// Clear selected project when team changes
|
||||
projectId: ''
|
||||
}));
|
||||
}}
|
||||
@@ -291,10 +291,10 @@ function CreateShortUrlForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目选择 */}
|
||||
{/* Project Selection */}
|
||||
<div>
|
||||
<label htmlFor="projectId" className="block text-sm font-medium text-gray-700">
|
||||
项目 <span className="text-red-500">*</span>
|
||||
Project <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<ProjectSelector
|
||||
@@ -310,10 +310,10 @@ function CreateShortUrlForm() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 描述 */}
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
|
||||
描述 <span className="text-gray-500">(可选)</span>
|
||||
Description <span className="text-gray-500">(Optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
@@ -321,15 +321,15 @@ function CreateShortUrlForm() {
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
rows={3}
|
||||
placeholder="对此链接的简短描述"
|
||||
placeholder="A brief description of this link"
|
||||
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 标签 */}
|
||||
{/* Tags */}
|
||||
<div>
|
||||
<label htmlFor="tagInput" className="block text-sm font-medium text-gray-700">
|
||||
标签 <span className="text-gray-500">(可选)</span>
|
||||
Tags <span className="text-gray-500">(Optional)</span>
|
||||
</label>
|
||||
<div className="flex mt-1 rounded-md shadow-sm">
|
||||
<input
|
||||
@@ -338,7 +338,7 @@ function CreateShortUrlForm() {
|
||||
value={tagInput}
|
||||
onChange={(e) => setTagInput(e.target.value)}
|
||||
onKeyDown={handleTagKeyDown}
|
||||
placeholder="添加标签并按 Enter"
|
||||
placeholder="Add a tag and press Enter"
|
||||
className="flex-1 block w-full min-w-0 px-3 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
@@ -346,7 +346,7 @@ function CreateShortUrlForm() {
|
||||
onClick={addTag}
|
||||
className="inline-flex items-center px-3 py-2 text-sm font-medium text-white border border-transparent rounded-r-md shadow-sm bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
添加
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -360,7 +360,7 @@ function CreateShortUrlForm() {
|
||||
onClick={() => removeTag(tag)}
|
||||
className="flex-shrink-0 ml-1 text-blue-500 rounded-full hover:text-blue-700 focus:outline-none"
|
||||
>
|
||||
<span className="sr-only">删除标签 {tag}</span>
|
||||
<span className="sr-only">Remove tag {tag}</span>
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
@@ -371,14 +371,14 @@ function CreateShortUrlForm() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 提交按钮 */}
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 mr-3"
|
||||
>
|
||||
取消
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -391,9 +391,9 @@ function CreateShortUrlForm() {
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
处理中...
|
||||
Processing...
|
||||
</>
|
||||
) : '创建短链接'}
|
||||
) : 'Create Short URL'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -21,7 +21,7 @@ function MessageHandler({ setMessage }: { setMessage: (message: { type: string,
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const { signIn, user } = useAuth();
|
||||
const { signIn, signInWithGoogle, user } = useAuth();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
@@ -67,6 +67,28 @@ export default function LoginPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setMessage({ type: '', content: '' });
|
||||
|
||||
const { error } = await signInWithGoogle();
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
// Google OAuth will handle the redirect
|
||||
} catch (error) {
|
||||
console.error('Google login error:', error);
|
||||
setMessage({
|
||||
type: 'error',
|
||||
content: error instanceof Error ? error.message : 'Failed to sign in with Google'
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
{/* Wrap the component using useSearchParams in Suspense */}
|
||||
@@ -101,7 +123,31 @@ export default function LoginPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleEmailSignIn} className="mt-8 space-y-6">
|
||||
{/* Google Sign In Button */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleGoogleSignIn}
|
||||
disabled={isLoading}
|
||||
className="w-full flex items-center justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg className="h-5 w-5 mr-2" 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>
|
||||
{isLoading ? 'Signing in...' : 'Sign in with Google'}
|
||||
</button>
|
||||
|
||||
<div className="mt-6 flex items-center justify-center">
|
||||
<div className="border-t border-gray-300 flex-grow mr-3"></div>
|
||||
<span className="text-sm text-gray-500">or</span>
|
||||
<div className="border-t border-gray-300 flex-grow ml-3"></div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleEmailSignIn} className="mt-6 space-y-6">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
||||
Email address
|
||||
@@ -144,7 +190,7 @@ export default function LoginPage() {
|
||||
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"
|
||||
>
|
||||
{isLoading ? 'Signing in...' : 'Sign in'}
|
||||
{isLoading ? 'Signing in...' : 'Sign in with Email'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -12,44 +12,44 @@ export default function RegisterPage() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { signUp, signInWithGoogle } = useAuth();
|
||||
|
||||
// 处理注册表单提交
|
||||
// Handle registration form submission
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
// 验证密码
|
||||
// Validate passwords
|
||||
if (password !== confirmPassword) {
|
||||
setError('两次输入的密码不一致');
|
||||
setError('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
// 密码强度验证
|
||||
// Password strength validation
|
||||
if (password.length < 6) {
|
||||
setError('密码长度至少为6个字符');
|
||||
setError('Password must be at least 6 characters');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await signUp(email, password);
|
||||
// 注册成功后会跳转到登录页面,提示用户验证邮箱
|
||||
// After successful registration, redirect to login page with email verification prompt
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
setError('注册失败,请稍后再试或使用其他邮箱');
|
||||
setError('Registration failed. Please try again later or use a different email');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理Google注册/登录
|
||||
// Handle Google registration/login
|
||||
const handleGoogleSignIn = async () => {
|
||||
setError(null);
|
||||
try {
|
||||
await signInWithGoogle();
|
||||
// 登录流程会重定向到Google,然后回到应用
|
||||
// Login flow will redirect to Google and then back to the application
|
||||
} catch (error) {
|
||||
console.error('Google sign in error:', error);
|
||||
setError('Google登录失败,请稍后再试');
|
||||
setError('Google login failed. Please try again later');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,13 +57,13 @@ export default function RegisterPage() {
|
||||
<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>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">Register</h1>
|
||||
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
创建您的帐户以访问分析仪表板
|
||||
Create your account to access the analytics dashboard
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{/* Error message */}
|
||||
{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}
|
||||
@@ -74,7 +74,7 @@ export default function RegisterPage() {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
邮箱地址
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
@@ -90,7 +90,7 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
密码
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
@@ -106,7 +106,7 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
确认密码
|
||||
Confirm Password
|
||||
</label>
|
||||
<input
|
||||
id="confirmPassword"
|
||||
@@ -128,7 +128,7 @@ export default function RegisterPage() {
|
||||
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 ? '注册中...' : '注册'}
|
||||
{isLoading ? 'Registering...' : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@ export default function RegisterPage() {
|
||||
<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>
|
||||
<span className="px-2 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400">or</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -173,19 +173,19 @@ export default function RegisterPage() {
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
使用Google账号注册
|
||||
Sign up with Google
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
已有账号?{' '}
|
||||
Already have an account?{' '}
|
||||
<Link
|
||||
href="/login"
|
||||
className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
>
|
||||
登录
|
||||
Log in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
22
middleware.ts
Normal file
22
middleware.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export async function middleware(req: NextRequest) {
|
||||
const res = NextResponse.next();
|
||||
|
||||
// Create a Supabase client configured to use cookies
|
||||
const supabase = createMiddlewareClient({ req, res });
|
||||
|
||||
// Refresh session if expired - required for Server Components
|
||||
await supabase.auth.getSession();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Specify the paths where this middleware should run
|
||||
export const config = {
|
||||
matcher: [
|
||||
'/((?!_next/static|_next/image|favicon.ico).*)',
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user