forked from 77media/video-flow
处理谷歌登入
This commit is contained in:
parent
b8bae86ae7
commit
69be9677fe
56
app/api/auth/google/callback/route.ts
Normal file
56
app/api/auth/google/callback/route.ts
Normal 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
15
app/signup/layout.tsx
Normal 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
131
app/signup/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,16 +1,65 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import './style/login.css';
|
import './style/login.css';
|
||||||
import VantaHaloBackground from '@/components/vanta-halo-background';
|
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() {
|
export default function Login() {
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
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(() => {
|
const handleBackgroundLoaded = useCallback(() => {
|
||||||
setIsLoaded(true);
|
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 (
|
return (
|
||||||
<div className="main-container login-page relative">
|
<div className="main-container login-page relative">
|
||||||
{/* logo Movie Flow */}
|
{/* logo Movie Flow */}
|
||||||
@ -28,22 +77,69 @@ export default function Login() {
|
|||||||
<p>输入您的凭据以访问您的账户</p>
|
<p>输入您的凭据以访问您的账户</p>
|
||||||
</div>
|
</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">
|
<div className="mb-3">
|
||||||
<label className="form-label">电子邮箱地址</label>
|
<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>
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">密码</label>
|
<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">
|
<div className="d-flex justify-content-end mt-1">
|
||||||
<a className="auth-link small" href="/forgot-password" data-discover="true">忘记密码?</a>
|
<a className="auth-link small" href="/forgot-password" data-discover="true">忘记密码?</a>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="text-center mt-3">
|
||||||
<p style={{ color: "rgba(255, 255, 255, 0.6)" }}>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
70
lib/auth.ts
Normal file
70
lib/auth.ts
Normal 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';
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user