mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-26 15:47:00 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
114
app/auth/oauth/callback/page.tsx
Normal file
114
app/auth/oauth/callback/page.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { oauthService } from '@/lib/services/auth';
|
||||
import { Loader2, AlertCircle } from 'lucide-react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export default function OAuthCallbackPage() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isProcessing, setIsProcessing] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const handleCallback = async () => {
|
||||
const code = searchParams.get('code');
|
||||
const state = searchParams.get('state');
|
||||
const error = searchParams.get('error');
|
||||
const provider = searchParams.get('provider');
|
||||
|
||||
// Check for OAuth error
|
||||
if (error) {
|
||||
setError(`OAuth error: ${error}`);
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (!code || !state || !provider) {
|
||||
setError('Invalid OAuth callback - missing required parameters');
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate provider
|
||||
if (provider !== 'google' && provider !== 'discord') {
|
||||
setError(`Unsupported OAuth provider: ${provider}`);
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle the OAuth callback
|
||||
const { redirectUrl } = await oauthService.handleOAuthCallback(
|
||||
provider as 'google' | 'discord',
|
||||
code,
|
||||
state
|
||||
);
|
||||
|
||||
// Redirect to the intended destination
|
||||
router.push(redirectUrl || '/dashboard');
|
||||
} catch (err: any) {
|
||||
console.error('OAuth callback error:', err);
|
||||
const errorMessage =
|
||||
err.response?.data?.detail ||
|
||||
err.message ||
|
||||
'Failed to complete OAuth login';
|
||||
setError(errorMessage);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleCallback();
|
||||
}, [searchParams, router]);
|
||||
|
||||
if (isProcessing) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="text-center space-y-4">
|
||||
<Loader2 className="h-12 w-12 animate-spin mx-auto text-primary" />
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold">Signing you in...</h2>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
Please wait while we complete your authentication
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||
<div className="max-w-md w-full space-y-4">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Authentication Failed</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
onClick={() => router.push('/login')}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
Back to Login
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => window.location.reload()}
|
||||
className="flex-1"
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
164
app/dashboard/page.tsx
Normal file
164
app/dashboard/page.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* Dashboard Page
|
||||
*
|
||||
* Protected page that displays user information and account details
|
||||
*/
|
||||
|
||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { user, isAuthenticated, isLoading, logout } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
// Redirect to home if not authenticated
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
router.push('/');
|
||||
}
|
||||
}, [isLoading, isAuthenticated, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated || !user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/" className="flex items-center gap-2 hover:opacity-80 transition-opacity">
|
||||
<h1 className="text-2xl font-bold text-gray-900">ThrillWiki</h1>
|
||||
</Link>
|
||||
<span className="text-sm text-gray-400">|</span>
|
||||
<span className="text-sm font-medium text-gray-600">Dashboard</span>
|
||||
</div>
|
||||
<Button onClick={handleLogout} variant="outline" size="sm">
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
Welcome, {user.username}!
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
Manage your account and view your activity
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* User Profile Card */}
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="h-24 w-24 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-white text-4xl font-bold mb-4">
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-1">{user.username}</h3>
|
||||
<p className="text-gray-600 mb-4">{user.email}</p>
|
||||
|
||||
<div className="w-full space-y-2 text-sm">
|
||||
<div className="flex justify-between py-2 border-t">
|
||||
<span className="text-gray-600">User ID:</span>
|
||||
<span className="font-mono text-gray-900">{user.id}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-t">
|
||||
<span className="text-gray-600">Email Verified:</span>
|
||||
<span className={user.email_verified ? 'text-green-600' : 'text-orange-600'}>
|
||||
{user.email_verified ? '✓ Yes' : '✗ No'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-t">
|
||||
<span className="text-gray-600">Account Status:</span>
|
||||
<span className="text-green-600">Active</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Activity Section */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
||||
<h3 className="text-xl font-bold mb-4">Quick Actions</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button className="p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors text-left">
|
||||
<h4 className="font-semibold mb-2">Browse Parks</h4>
|
||||
<p className="text-sm text-gray-600">Explore theme parks worldwide</p>
|
||||
</button>
|
||||
<button className="p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors text-left">
|
||||
<h4 className="font-semibold mb-2">Browse Rides</h4>
|
||||
<p className="text-sm text-gray-600">Discover roller coasters and attractions</p>
|
||||
</button>
|
||||
<button className="p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors text-left">
|
||||
<h4 className="font-semibold mb-2">My Reviews</h4>
|
||||
<p className="text-sm text-gray-600">View and manage your reviews</p>
|
||||
</button>
|
||||
<button className="p-4 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors text-left">
|
||||
<h4 className="font-semibold mb-2">Settings</h4>
|
||||
<p className="text-sm text-gray-600">Update your profile and preferences</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<h3 className="text-xl font-bold mb-4">Recent Activity</h3>
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<p>No recent activity to display</p>
|
||||
<p className="text-sm mt-2">Start exploring to see your activity here!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Preview */}
|
||||
<div className="mt-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg shadow-lg p-6 text-white">
|
||||
<h3 className="text-2xl font-bold mb-2">Coming Soon</h3>
|
||||
<p className="mb-4">
|
||||
More features are being developed including park browsing, ride reviews, and social features.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
||||
<div className="bg-white/10 rounded p-3">
|
||||
<h4 className="font-semibold mb-1">🗺️ Interactive Maps</h4>
|
||||
<p className="text-sm opacity-90">Explore parks with interactive maps</p>
|
||||
</div>
|
||||
<div className="bg-white/10 rounded p-3">
|
||||
<h4 className="font-semibold mb-1">📊 Statistics</h4>
|
||||
<p className="text-sm opacity-90">Track your coaster count and stats</p>
|
||||
</div>
|
||||
<div className="bg-white/10 rounded p-3">
|
||||
<h4 className="font-semibold mb-1">👥 Social Features</h4>
|
||||
<p className="text-sm opacity-90">Connect with other enthusiasts</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
app/error.tsx
Normal file
24
app/error.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center p-24">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
|
||||
<p className="text-gray-600 mb-4">{error.message}</p>
|
||||
<button
|
||||
onClick={reset}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
59
app/globals.css
Normal file
59
app/globals.css
Normal file
@@ -0,0 +1,59 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
25
app/layout.tsx
Normal file
25
app/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import { AuthProvider } from '@/lib/contexts/AuthContext';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'ThrillWiki - Roller Coaster Database',
|
||||
description: 'Comprehensive database of theme parks, roller coasters, and attractions worldwide',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
10
app/loading.tsx
Normal file
10
app/loading.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="h-32 w-32 animate-spin rounded-full border-b-2 border-t-2 border-gray-900"></div>
|
||||
<p className="mt-4 text-lg">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
app/not-found.tsx
Normal file
18
app/not-found.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center p-24">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold mb-4">404 - Page Not Found</h2>
|
||||
<p className="text-gray-600 mb-4">Could not find the requested resource</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
Return Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
114
app/page.tsx
Normal file
114
app/page.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
'use client';
|
||||
|
||||
import { UserNav } from '@/components/auth/UserNav';
|
||||
import { useAuth } from '@/lib/contexts/AuthContext';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function HomePage() {
|
||||
const { isAuthenticated, user, isLoading } = useAuth();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||
{/* Header */}
|
||||
<header className="bg-white shadow-sm">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-2xl font-bold text-gray-900">ThrillWiki</h1>
|
||||
<span className="text-sm text-gray-500">Roller Coaster Database</span>
|
||||
</div>
|
||||
<UserNav />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<div className="text-center">
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
Welcome to ThrillWiki
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 mb-8">
|
||||
The comprehensive database of theme parks, roller coasters, and attractions worldwide
|
||||
</p>
|
||||
|
||||
{!isLoading && (
|
||||
<div className="mt-12">
|
||||
{isAuthenticated && user ? (
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-2xl mx-auto">
|
||||
<h3 className="text-2xl font-bold mb-4">
|
||||
Welcome back, {user.username}!
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
You're successfully logged in. Explore the features below:
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="p-6 bg-blue-50 rounded-lg hover:bg-blue-100 transition-colors"
|
||||
>
|
||||
<h4 className="font-semibold text-lg mb-2">Dashboard</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
View your profile and activity
|
||||
</p>
|
||||
</Link>
|
||||
<div className="p-6 bg-gray-50 rounded-lg opacity-50 cursor-not-allowed">
|
||||
<h4 className="font-semibold text-lg mb-2">Browse Parks</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Coming soon...
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-50 rounded-lg opacity-50 cursor-not-allowed">
|
||||
<h4 className="font-semibold text-lg mb-2">Browse Rides</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Coming soon...
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 bg-gray-50 rounded-lg opacity-50 cursor-not-allowed">
|
||||
<h4 className="font-semibold text-lg mb-2">My Reviews</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Coming soon...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-2xl mx-auto">
|
||||
<h3 className="text-2xl font-bold mb-4">Get Started</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Sign up or log in to access all features of ThrillWiki
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-blue-50 rounded-lg text-left">
|
||||
<h4 className="font-semibold mb-2">✨ Track Your Visits</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Keep a record of all the theme parks you've visited
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-blue-50 rounded-lg text-left">
|
||||
<h4 className="font-semibold mb-2">🎢 Rate Roller Coasters</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Share your experiences and read reviews from other enthusiasts
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-blue-50 rounded-lg text-left">
|
||||
<h4 className="font-semibold mb-2">🌍 Explore Worldwide</h4>
|
||||
<p className="text-sm text-gray-600">
|
||||
Browse parks and attractions from around the globe
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-white border-t border-gray-200 mt-16">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 text-center text-gray-600">
|
||||
<p>© 2025 ThrillWiki. Authentication powered by Django + JWT.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user