From 69be9677fe3ed2faea59c739f8c02d279096f4c0 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 30 Jun 2025 17:16:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E8=B0=B7=E6=AD=8C=E7=99=BB?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/auth/google/callback/route.ts | 56 +++++++++++ app/signup/layout.tsx | 15 +++ app/signup/page.tsx | 131 ++++++++++++++++++++++++++ components/pages/login.tsx | 108 +++++++++++++++++++-- lib/auth.ts | 70 ++++++++++++++ 5 files changed, 374 insertions(+), 6 deletions(-) create mode 100644 app/api/auth/google/callback/route.ts create mode 100644 app/signup/layout.tsx create mode 100644 app/signup/page.tsx create mode 100644 lib/auth.ts diff --git a/app/api/auth/google/callback/route.ts b/app/api/auth/google/callback/route.ts new file mode 100644 index 0000000..1c5c729 --- /dev/null +++ b/app/api/auth/google/callback/route.ts @@ -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)); + } +} \ No newline at end of file diff --git a/app/signup/layout.tsx b/app/signup/layout.tsx new file mode 100644 index 0000000..3bb84d8 --- /dev/null +++ b/app/signup/layout.tsx @@ -0,0 +1,15 @@ +'use client'; + +import React from 'react'; + +export default function SignupLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/app/signup/page.tsx b/app/signup/page.tsx new file mode 100644 index 0000000..4d6864d --- /dev/null +++ b/app/signup/page.tsx @@ -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) => { + 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 ( +
+
+

Sign Up, for free

+ +
+
+ + 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" + /> +
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+ + {formError && ( +
{formError}
+ )} + +
+ + Back to home + + +
+
+ +
+
+ or +
+
+ + + + {/* Success message */} +
+ Thank you! Your submission has been received! +
+ + {/* Error message */} +
+ Oops! Something went wrong while submitting the form. +
+
+
+ ); +} \ No newline at end of file diff --git a/components/pages/login.tsx b/components/pages/login.tsx index 0d156f5..92fed4a 100644 --- a/components/pages/login.tsx +++ b/components/pages/login.tsx @@ -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) => { + 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 (
{/* logo Movie Flow */} @@ -28,22 +77,69 @@ export default function Login() {

输入您的凭据以访问您的账户

-
+ + {successMessage && ( +
+ {successMessage} +
+ )} +
- + setEmail(e.target.value)} + />
- + setPassword(e.target.value)} + />
- + + {formError && ( +
{formError}
+ )} + + + +
+
+ +
+
+ + +

- 还没有账户? 注册 + 还没有账户? 注册

diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..edb7cb6 --- /dev/null +++ b/lib/auth.ts @@ -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'; +}; \ No newline at end of file