Add Google OAuth integration, routing, and theme toggle; implement login and protected routes

This commit is contained in:
ishikabhoyar
2025-10-13 23:51:39 +05:30
parent e8e6011524
commit 453f44a43a
13 changed files with 714 additions and 17 deletions

View File

@@ -0,0 +1,41 @@
import { useAuth } from '../contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
const Header = () => {
const { user, logout } = useAuth();
const navigate = useNavigate();
const handleLogout = () => {
logout();
navigate('/login');
};
return (
<header className="bg-gray-800 border-b border-gray-700 px-4 py-2 flex items-center justify-between">
<div className="flex items-center space-x-4">
<h1 className="text-white font-bold text-lg">Monaco Editor</h1>
</div>
<div className="flex items-center space-x-4">
{user?.picture && (
<img
src={user.picture}
alt="Profile"
className="w-8 h-8 rounded-full"
/>
)}
<span className="text-gray-300 text-sm">
Welcome, {user?.name || user?.email}
</span>
<button
onClick={handleLogout}
className="bg-red-600 hover:bg-red-700 text-white text-sm px-3 py-1 rounded transition-colors duration-200"
>
Logout
</button>
</div>
</header>
);
};
export default Header;

View File

@@ -0,0 +1,161 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { GoogleLogin } from '@react-oauth/google';
import { useAuth } from '../contexts/AuthContext';
import ThemeToggle from './ThemeToggle';
const Login = () => {
const [error, setError] = useState('');
const navigate = useNavigate();
const { login } = useAuth();
// Check if user is already logged in
useEffect(() => {
const token = localStorage.getItem('monaco_token');
if (token) {
navigate('/editor');
}
}, [navigate]);
const handleLoginSuccess = async (credentialResponse) => {
console.log('Google login success:', credentialResponse);
if (credentialResponse.credential) {
try {
console.log('Processing Google credential...');
// For demo purposes, we'll decode the JWT token to get user info
// In a real app, you would send this to your backend for verification
const base64Url = credentialResponse.credential.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
const userInfo = JSON.parse(jsonPayload);
console.log('User info:', userInfo);
const success = await login(userInfo.email, credentialResponse.credential, userInfo);
if (success) {
navigate('/editor');
} else {
throw new Error('Authentication failed');
}
} catch (error) {
console.error('Error during login:', error);
setError(`Authentication failed: ${error.message || 'Please try again.'}`);
}
} else {
console.error('No credential received from Google');
setError('No credential received from Google. Please try again.');
}
};
const handleLoginError = () => {
console.error('Google login failed - checking network and configuration...');
// Check if we're online
if (!navigator.onLine) {
setError('You appear to be offline. Please check your internet connection and try again.');
return;
}
// Check if Google Identity Services script loaded
if (typeof window !== 'undefined' && !window.google) {
console.error('Google Identity Services script not loaded');
setError('Google authentication service is not available. Please refresh the page and try again.');
return;
}
setError('Google login failed. This might be due to network connectivity issues, browser compatibility, or Google account configuration. Please try again or contact support if the problem persists.');
};
return (
<div className="login-container">
{/* Theme Toggle - Top Right */}
<div className="theme-toggle">
<ThemeToggle />
</div>
{/* Left Side - Background Image */}
<div className="login-left">
<img
src="/BG-login(2).jpg"
alt="Login Background"
className="login-bg-image"
/>
</div>
{/* Right Side - Login Form */}
<div className="login-right">
<div className="login-form-container">
{/* Logos */}
<div className="login-logos">
<img
src="/Vidyavihar@3x.png"
alt="KJSCE"
className="login-logo"
/>
<img
src="/kjsce2x.png"
alt="Somaiya Vidyavihar"
className="login-logo"
/>
</div>
{/* Title */}
<h1 className="login-title">
Welcome To Monaco Editor
</h1>
<p className="login-subtitle">
Please sign in with your Google account to continue.
</p>
{/* Error Display */}
{error && (
<div className="login-error">
<p style={{ fontSize: '0.875rem', margin: 0 }}>{error}</p>
</div>
)}
{/* Google Login Button */}
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.5rem' }}>
<GoogleLogin
onSuccess={handleLoginSuccess}
onError={handleLoginError}
useOneTap
/>
</div>
<div className="login-footer">
<p className="login-footer-text">
Need help?{' '}
<button className="login-footer-link">
Contact admin
</button>
</p>
</div>
<div className="login-demo-note">
<p className="login-demo-text">
Sign in with your Google account to access the Monaco Editor
</p>
</div>
{/* Trust Logo */}
<div className="login-trust-logo">
<img
src="/Bottom.png"
alt="Somaiya Trust"
className="trust-logo-img"
/>
</div>
</div>
</div>
</div>
);
};
export default Login;

View File

@@ -0,0 +1,21 @@
import { Navigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const ProtectedRoute = ({ children }) => {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-black">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-white"></div>
<p className="mt-4 text-white">Loading...</p>
</div>
</div>
);
}
return isAuthenticated ? children : <Navigate to="/login" replace />;
};
export default ProtectedRoute;

View File

@@ -0,0 +1,61 @@
import { useState, useEffect } from 'react';
const ThemeToggle = () => {
const [theme, setTheme] = useState('dark');
useEffect(() => {
// Check if there's a saved theme preference
const savedTheme = localStorage.getItem('monaco_theme');
if (savedTheme) {
setTheme(savedTheme);
} else {
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(prefersDark ? 'dark' : 'light');
}
}, []);
useEffect(() => {
// Update the HTML class and CSS variables
const root = document.documentElement;
root.className = theme;
// Save theme preference
localStorage.setItem('monaco_theme', theme);
}, [theme]);
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<button
onClick={toggleTheme}
className="relative p-2 rounded-md border border-gray-600 bg-gray-800 hover:bg-gray-700 transition-colors"
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
>
{/* Sun Icon for Light Mode */}
<svg
className={`h-5 w-5 transition-all ${theme === 'dark' ? 'rotate-90 scale-0' : 'rotate-0 scale-100'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<circle cx="12" cy="12" r="5" />
<path d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
</svg>
{/* Moon Icon for Dark Mode */}
<svg
className={`absolute top-2 left-2 h-5 w-5 transition-all ${theme === 'dark' ? 'rotate-0 scale-100' : 'rotate-90 scale-0'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
</button>
);
};
export default ThemeToggle;