处理谷歌登入

This commit is contained in:
Xin Wang 2025-06-30 17:16:15 +08:00
parent b8bae86ae7
commit 69be9677fe
5 changed files with 374 additions and 6 deletions

View File

@ -0,0 +1,56 @@
import { NextRequest, NextResponse } from 'next/server';
/**
* Handle Google OAuth callback
* In a real app, this would:
* 1. Exchange the authorization code for tokens
* 2. Verify the token and get user info from Google
* 3. Create or update the user in your database
* 4. Set session/cookies
* 5. Redirect to the app
*/
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get('code');
const error = searchParams.get('error');
// Handle errors from Google
if (error) {
console.error('Google OAuth error:', error);
return NextResponse.redirect(new URL('/login?error=google_oauth', request.url));
}
if (!code) {
console.error('No authorization code received from Google');
return NextResponse.redirect(new URL('/login?error=no_code', request.url));
}
try {
// In a real app, you would exchange the code for tokens
// and validate the tokens here
// For this demo, we'll just simulate a successful login
// by redirecting with a mock session token
const redirectUrl = new URL('/', request.url);
// Mock user data that would normally come from Google
const mockUser = {
id: 'google-123456',
name: 'Google User',
email: 'user@gmail.com',
picture: 'https://i.pravatar.cc/150',
};
// In a real app, you would set cookies or session data here
// Simulate setting a session by adding a URL parameter
// In a real app, don't pass sensitive data in URL parameters
redirectUrl.searchParams.set('session', 'demo-session-token');
redirectUrl.searchParams.set('user', encodeURIComponent(JSON.stringify(mockUser)));
return NextResponse.redirect(redirectUrl);
} catch (error) {
console.error('Failed to process Google authentication:', error);
return NextResponse.redirect(new URL('/login?error=auth_failed', request.url));
}
}

15
app/signup/layout.tsx Normal file
View File

@ -0,0 +1,15 @@
'use client';
import React from 'react';
export default function SignupLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="bg-black min-h-screen">
{children}
</div>
);
}

131
app/signup/page.tsx Normal file
View File

@ -0,0 +1,131 @@
'use client';
import React, { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { signInWithGoogle } from '@/lib/auth';
export default function SignupPage() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [formError, setFormError] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
setFormError('');
try {
// Here you would connect to your backend API for registration
console.log('Signing up', { name, email, password });
// Simulate successful registration
setTimeout(() => {
// Redirect to login page with success message
router.push('/login?registered=true');
}, 1500);
} catch (error) {
console.error('Signup error:', error);
setFormError('Something went wrong. Please try again.');
} finally {
setIsSubmitting(false);
}
};
const handleGoogleSignIn = () => {
signInWithGoogle();
};
return (
<div className="min-h-screen bg-[#0C0E11] flex items-center justify-center p-4">
<div className="w-full max-w-md bg-[#13151d]/90 backdrop-blur-xl rounded-3xl p-8 shadow-xl">
<h1 className="text-4xl font-bold text-white mb-8 text-center">Sign Up, for free</h1>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-white mb-1">Name</label>
<input
type="text"
placeholder="Your name"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full px-4 py-3 rounded-lg bg-[#1c1e29] border border-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-white mb-1">Email</label>
<input
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-4 py-3 rounded-lg bg-[#1c1e29] border border-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-white mb-1">Password</label>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full px-4 py-3 rounded-lg bg-[#1c1e29] border border-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
{formError && (
<div className="text-red-500 text-sm">{formError}</div>
)}
<div className="flex gap-4 mt-8">
<Link
href="/login"
className="flex-1 py-3 text-center border border-gray-600 rounded-lg text-white hover:bg-white/10 transition-colors"
>
Back to home
</Link>
<button
type="submit"
disabled={isSubmitting}
className="flex-1 py-3 rounded-lg bg-purple-600 hover:bg-purple-700 text-white font-medium transition-colors disabled:opacity-70"
>
{isSubmitting ? 'Signing up...' : 'Sign Up'}
</button>
</div>
</form>
<div className="my-6 relative flex items-center">
<div className="flex-grow border-t border-gray-700"></div>
<span className="flex-shrink mx-4 text-gray-400">or</span>
<div className="flex-grow border-t border-gray-700"></div>
</div>
<button
onClick={handleGoogleSignIn}
className="w-full flex items-center justify-center gap-3 py-3 border border-gray-600 rounded-lg text-white hover:bg-white/5 transition-colors"
>
<img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg" className="w-5 h-5" alt="Google" />
Continue with Google
</button>
{/* Success message */}
<div className="hidden text-green-400 text-sm mt-4 text-center">
Thank you! Your submission has been received!
</div>
{/* Error message */}
<div className="hidden text-red-400 text-sm mt-4 text-center">
Oops! Something went wrong while submitting the form.
</div>
</div>
</div>
);
}

View File

@ -1,16 +1,65 @@
'use client';
import { useState, useCallback } from 'react';
import { useState, useCallback, useEffect } from 'react';
import './style/login.css';
import VantaHaloBackground from '@/components/vanta-halo-background';
import { useRouter, useSearchParams } from 'next/navigation';
import React from 'react';
import Link from 'next/link';
import { signInWithGoogle, loginUser } from '@/lib/auth';
export default function Login() {
const [isLoaded, setIsLoaded] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [formError, setFormError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const router = useRouter();
const searchParams = useSearchParams();
// Check for registered=true parameter
useEffect(() => {
const registered = searchParams?.get('registered');
if (registered === 'true') {
setSuccessMessage('注册成功!请使用您的新帐号登录。');
}
const error = searchParams?.get('error');
if (error) {
if (error === 'google_oauth') {
setFormError('Google登录失败请重试。');
} else if (error === 'auth_failed') {
setFormError('身份验证失败,请重试。');
}
}
}, [searchParams]);
const handleBackgroundLoaded = useCallback(() => {
setIsLoaded(true);
}, []);
const handleGoogleSignIn = () => {
signInWithGoogle();
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
setFormError('');
setSuccessMessage('');
try {
await loginUser(email, password);
router.push('/');
} catch (error) {
console.error('Login failed:', error);
setFormError('登录失败,请检查您的凭据后重试。');
} finally {
setIsSubmitting(false);
}
};
return (
<div className="main-container login-page relative">
{/* logo Movie Flow */}
@ -28,22 +77,69 @@ export default function Login() {
<p>访</p>
</div>
<form className="">
<form onSubmit={handleSubmit} className="">
{successMessage && (
<div className="bg-green-500/20 text-green-300 p-3 mb-4 rounded-lg border border-green-500/20">
{successMessage}
</div>
)}
<div className="mb-3">
<label className="form-label"></label>
<input placeholder="电子邮箱地址" required className="form-control" type="email" defaultValue="" />
<input
placeholder="电子邮箱地址"
required
className="form-control"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="mb-3">
<label className="form-label"></label>
<input placeholder="密码" required className="form-control" type="password" defaultValue="" />
<input
placeholder="密码"
required
className="form-control"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<div className="d-flex justify-content-end mt-1">
<a className="auth-link small" href="/forgot-password" data-discover="true"></a>
</div>
</div>
<button type="submit" className="w-full mt-4 btn btn-primary"></button>
{formError && (
<div className="text-red-500 text-sm mb-3">{formError}</div>
)}
<button
type="submit"
className="w-full mt-4 btn btn-primary"
disabled={isSubmitting}
>
{isSubmitting ? '登录中...' : '登录'}
</button>
<div className="my-4 relative flex items-center">
<div className="flex-grow border-t border-gray-500/30"></div>
<span className="flex-shrink mx-4 text-gray-400"></span>
<div className="flex-grow border-t border-gray-500/30"></div>
</div>
<button
type="button"
onClick={handleGoogleSignIn}
className="w-full flex items-center justify-center gap-2 py-3 border border-white/20 rounded-lg bg-black/30 hover:bg-black/50 backdrop-blur-sm transition-all"
>
<img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg" className="w-5 h-5" alt="Google" />
<span>Continue with Google</span>
</button>
<div className="text-center mt-3">
<p style={{ color: "rgba(255, 255, 255, 0.6)" }}>
<a className="auth-link" href="/signup" data-discover="true"></a>
<Link href="/signup" className="auth-link"></Link>
</p>
</div>
</form>

70
lib/auth.ts Normal file
View File

@ -0,0 +1,70 @@
// Mock Google OAuth configuration
const GOOGLE_CLIENT_ID = 'your-google-client-id';
const GOOGLE_REDIRECT_URI = typeof window !== 'undefined'
? `${window.location.origin}/api/auth/google/callback`
: '';
/**
* Initiates Google OAuth authentication flow
*/
export const signInWithGoogle = () => {
const params = new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
redirect_uri: GOOGLE_REDIRECT_URI,
response_type: 'code',
scope: 'email profile',
prompt: 'select_account',
});
// In a real implementation, you would have proper error handling and secure state management
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
};
/**
* Gets the current user from session storage (mock implementation)
*/
export const getCurrentUser = () => {
if (typeof window === 'undefined') return null;
const userJson = sessionStorage.getItem('currentUser');
if (!userJson) return null;
try {
return JSON.parse(userJson);
} catch (error) {
console.error('Failed to parse user data from session', error);
return null;
}
};
/**
* Handles user login (mock implementation)
*/
export const loginUser = async (email: string, password: string) => {
// This is a mock implementation
// In a real app, you would make an API call to your backend
return new Promise((resolve) => {
setTimeout(() => {
// Mock user data
const user = {
id: '123',
name: 'Test User',
email,
avatar: 'https://i.pravatar.cc/150?u=' + email,
};
// Store in session storage (just for demo purposes)
sessionStorage.setItem('currentUser', JSON.stringify(user));
resolve(user);
}, 1000);
});
};
/**
* Handles user logout
*/
export const logoutUser = () => {
if (typeof window === 'undefined') return;
sessionStorage.removeItem('currentUser');
window.location.href = '/login';
};