feat: implement authentication flow with login, registration, and OAuth support
This commit is contained in:
102
src/pages/AuthCallbackPage.tsx
Normal file
102
src/pages/AuthCallbackPage.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { useAuth } from '@/contexts/AuthContext'
|
||||
import { apiService } from '@/lib/api'
|
||||
|
||||
export function AuthCallbackPage() {
|
||||
const navigate = useNavigate()
|
||||
const { setUser } = useAuth()
|
||||
const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing')
|
||||
const [error, setError] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
const handleOAuthCallback = async () => {
|
||||
try {
|
||||
// Get the code from URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const code = urlParams.get('code')
|
||||
|
||||
if (!code) {
|
||||
throw new Error('No authorization code received')
|
||||
}
|
||||
|
||||
console.log('Exchanging OAuth code for tokens...')
|
||||
|
||||
// Exchange the temporary code for proper auth cookies
|
||||
const response = await fetch('http://localhost:8000/api/v1/auth/exchange-oauth-token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ code }),
|
||||
credentials: 'include', // Important for setting cookies
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null)
|
||||
throw new Error(errorData?.detail || 'Token exchange failed')
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
console.log('Token exchange successful:', result)
|
||||
|
||||
// Now get the user info
|
||||
const user = await apiService.getMe()
|
||||
console.log('User info retrieved:', user)
|
||||
|
||||
// Update auth context
|
||||
if (setUser) setUser(user)
|
||||
|
||||
setStatus('success')
|
||||
|
||||
// Redirect to dashboard after a short delay
|
||||
setTimeout(() => {
|
||||
navigate('/')
|
||||
}, 1000)
|
||||
|
||||
} catch (error) {
|
||||
console.error('OAuth callback failed:', error)
|
||||
setError(error instanceof Error ? error.message : 'Authentication failed')
|
||||
setStatus('error')
|
||||
|
||||
// Redirect to login after error
|
||||
setTimeout(() => {
|
||||
navigate('/login')
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
handleOAuthCallback()
|
||||
}, [navigate, setUser])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="max-w-md w-full space-y-6 text-center">
|
||||
{status === 'processing' && (
|
||||
<div>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
||||
<h2 className="mt-4 text-xl font-semibold">Completing sign in...</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400">Please wait while we set up your account.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'success' && (
|
||||
<div>
|
||||
<div className="text-green-600 text-4xl mb-4">✓</div>
|
||||
<h2 className="text-xl font-semibold text-green-600">Sign in successful!</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400">Redirecting to dashboard...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<div>
|
||||
<div className="text-red-600 text-4xl mb-4">✗</div>
|
||||
<h2 className="text-xl font-semibold text-red-600">Sign in failed</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">{error}</p>
|
||||
<p className="text-sm text-gray-500">Redirecting to login page...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
38
src/pages/DashboardPage.tsx
Normal file
38
src/pages/DashboardPage.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { ModeToggle } from '../components/ModeToggle'
|
||||
|
||||
export function DashboardPage() {
|
||||
const { user, logout } = useAuth()
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<nav className="shadow-sm border-b">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16 items-center">
|
||||
<h1 className="text-xl font-semibold">Soundboard</h1>
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Welcome, {user?.name}
|
||||
</span>
|
||||
<ModeToggle />
|
||||
<button
|
||||
onClick={() => logout()}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6 sm:px-0">
|
||||
<div className="border-4 border-dashed border-gray-200 dark:border-gray-700 rounded-lg h-96 flex items-center justify-center">
|
||||
<p className="text-gray-500 dark:text-gray-400">Dashboard content coming soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
src/pages/LoginPage.tsx
Normal file
24
src/pages/LoginPage.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Link } from 'react-router'
|
||||
import { LoginForm } from '@/components/auth/LoginForm'
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
<LoginForm />
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Don't have an account?{' '}
|
||||
<Link
|
||||
to="/register"
|
||||
className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400"
|
||||
>
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
24
src/pages/RegisterPage.tsx
Normal file
24
src/pages/RegisterPage.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Link } from 'react-router'
|
||||
import { RegisterForm } from '@/components/auth/RegisterForm'
|
||||
|
||||
export function RegisterPage() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="w-full max-w-md space-y-6">
|
||||
<RegisterForm />
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Already have an account?{' '}
|
||||
<Link
|
||||
to="/login"
|
||||
className="font-medium text-blue-600 hover:text-blue-500 dark:text-blue-400"
|
||||
>
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user