diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json
index 0b733ce..0dab426 100644
--- a/Frontend/package-lock.json
+++ b/Frontend/package-lock.json
@@ -9,11 +9,13 @@
"version": "0.0.0",
"dependencies": {
"@monaco-editor/react": "^4.7.0",
+ "@react-oauth/google": "^0.12.2",
"lucide-react": "^0.483.0",
"monaco-editor": "^0.52.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-resizable-panels": "^2.1.7"
+ "react-resizable-panels": "^2.1.7",
+ "react-router-dom": "^7.9.4"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
@@ -1041,6 +1043,16 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/@react-oauth/google": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz",
+ "integrity": "sha512-d1GVm2uD4E44EJft2RbKtp8Z1fp/gK8Lb6KHgs3pHlM0PxCXGLaq8LLYQYENnN4xPWO1gkL4apBtlPKzpLvZwg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.36.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
@@ -1602,6 +1614,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2745,6 +2766,44 @@
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/react-router": {
+ "version": "7.9.4",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz",
+ "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.9.4",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz",
+ "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.9.4"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2810,6 +2869,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/Frontend/package.json b/Frontend/package.json
index 59d1853..8a5868f 100644
--- a/Frontend/package.json
+++ b/Frontend/package.json
@@ -11,11 +11,13 @@
},
"dependencies": {
"@monaco-editor/react": "^4.7.0",
+ "@react-oauth/google": "^0.12.2",
"lucide-react": "^0.483.0",
"monaco-editor": "^0.52.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-resizable-panels": "^2.1.7"
+ "react-resizable-panels": "^2.1.7",
+ "react-router-dom": "^7.9.4"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
diff --git a/Frontend/public/BG-login(2).jpg b/Frontend/public/BG-login(2).jpg
new file mode 100644
index 0000000..86c0f3a
Binary files /dev/null and b/Frontend/public/BG-login(2).jpg differ
diff --git a/Frontend/public/Bottom.png b/Frontend/public/Bottom.png
new file mode 100644
index 0000000..dc6805e
Binary files /dev/null and b/Frontend/public/Bottom.png differ
diff --git a/Frontend/public/Vidyavihar@3x.png b/Frontend/public/Vidyavihar@3x.png
new file mode 100644
index 0000000..8b62498
Binary files /dev/null and b/Frontend/public/Vidyavihar@3x.png differ
diff --git a/Frontend/public/kjsce2x.png b/Frontend/public/kjsce2x.png
new file mode 100644
index 0000000..6aa8433
Binary files /dev/null and b/Frontend/public/kjsce2x.png differ
diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx
index 8002bc3..b2fc964 100644
--- a/Frontend/src/App.jsx
+++ b/Frontend/src/App.jsx
@@ -1,20 +1,47 @@
-import CodeChallenge from "./components/CodeChallenge.jsx"
-import "./index.css"
+import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
+import { GoogleOAuthProvider } from '@react-oauth/google';
+import { AuthProvider } from './contexts/AuthContext';
+import Login from './components/Login';
+import CodeChallenge from "./components/CodeChallenge.jsx";
+import Header from './components/Header';
+import ProtectedRoute from './components/ProtectedRoute';
+import "./index.css";
function App() {
+ // Google OAuth Client ID - in production, this should be in environment variables
+ const GOOGLE_CLIENT_ID = "586378657128-smg8t52eqbji66c3eg967f70hsr54q5r.apps.googleusercontent.com";
+
return (
-
- )
+
+
+
+
+
+ } />
+
+
+
+
+
+ }
+ />
+ } />
+
+
+
+
+
+ );
}
export default App
diff --git a/Frontend/src/components/Header.jsx b/Frontend/src/components/Header.jsx
new file mode 100644
index 0000000..7eaf0dd
--- /dev/null
+++ b/Frontend/src/components/Header.jsx
@@ -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 (
+
+
+
Monaco Editor
+
+
+
+ {user?.picture && (
+

+ )}
+
+ Welcome, {user?.name || user?.email}
+
+
+
+
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/Frontend/src/components/Login.jsx b/Frontend/src/components/Login.jsx
new file mode 100644
index 0000000..3efe677
--- /dev/null
+++ b/Frontend/src/components/Login.jsx
@@ -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 (
+
+ {/* Theme Toggle - Top Right */}
+
+
+
+
+ {/* Left Side - Background Image */}
+
+
.jpg)
+
+
+ {/* Right Side - Login Form */}
+
+
+ {/* Logos */}
+
+

+

+
+
+ {/* Title */}
+
+ Welcome To Monaco Editor
+
+
+ Please sign in with your Google account to continue.
+
+
+ {/* Error Display */}
+ {error && (
+
+ )}
+
+ {/* Google Login Button */}
+
+
+
+
+
+
+ Need help?{' '}
+
+
+
+
+
+
+ Sign in with your Google account to access the Monaco Editor
+
+
+
+ {/* Trust Logo */}
+
+

+
+
+
+
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/Frontend/src/components/ProtectedRoute.jsx b/Frontend/src/components/ProtectedRoute.jsx
new file mode 100644
index 0000000..527ba5e
--- /dev/null
+++ b/Frontend/src/components/ProtectedRoute.jsx
@@ -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 (
+
+ );
+ }
+
+ return isAuthenticated ? children : ;
+};
+
+export default ProtectedRoute;
\ No newline at end of file
diff --git a/Frontend/src/components/ThemeToggle.jsx b/Frontend/src/components/ThemeToggle.jsx
new file mode 100644
index 0000000..0e4f6e2
--- /dev/null
+++ b/Frontend/src/components/ThemeToggle.jsx
@@ -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 (
+
+ );
+};
+
+export default ThemeToggle;
\ No newline at end of file
diff --git a/Frontend/src/contexts/AuthContext.jsx b/Frontend/src/contexts/AuthContext.jsx
new file mode 100644
index 0000000..ddde4c8
--- /dev/null
+++ b/Frontend/src/contexts/AuthContext.jsx
@@ -0,0 +1,98 @@
+import { createContext, useContext, useState, useEffect } from 'react';
+
+const AuthContext = createContext();
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Check for existing session on app load
+ useEffect(() => {
+ const checkAuthStatus = () => {
+ try {
+ const savedUser = localStorage.getItem('monaco_user');
+ const savedToken = localStorage.getItem('monaco_token');
+ if (savedUser && savedToken) {
+ setUser(JSON.parse(savedUser));
+ }
+ } catch (error) {
+ console.error('Error checking auth status:', error);
+ localStorage.removeItem('monaco_user');
+ localStorage.removeItem('monaco_token');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ checkAuthStatus();
+ }, []);
+
+ const login = async (email, googleToken, userInfo = null) => {
+ try {
+ // For Google OAuth login
+ if (googleToken && userInfo) {
+ const userData = {
+ id: userInfo.sub || Date.now(),
+ email: email,
+ name: userInfo.name || email.split('@')[0],
+ picture: userInfo.picture || null,
+ loginTime: new Date().toISOString(),
+ googleToken: googleToken
+ };
+
+ setUser(userData);
+ localStorage.setItem('monaco_user', JSON.stringify(userData));
+ localStorage.setItem('monaco_token', googleToken);
+ return true;
+ }
+
+ // Fallback for demo purposes (though we're moving to Google-only)
+ if (email && email.includes('@')) {
+ const userData = {
+ id: Date.now(),
+ email: email,
+ name: email.split('@')[0],
+ loginTime: new Date().toISOString()
+ };
+
+ setUser(userData);
+ localStorage.setItem('monaco_user', JSON.stringify(userData));
+ localStorage.setItem('monaco_token', 'demo_token');
+ return true;
+ }
+
+ return false;
+ } catch (error) {
+ console.error('Login error:', error);
+ return false;
+ }
+ };
+
+ const logout = () => {
+ setUser(null);
+ localStorage.removeItem('monaco_user');
+ localStorage.removeItem('monaco_token');
+ };
+
+ const value = {
+ user,
+ login,
+ logout,
+ isAuthenticated: !!user,
+ isLoading
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/Frontend/src/index.css b/Frontend/src/index.css
index edf7378..4d1145a 100644
--- a/Frontend/src/index.css
+++ b/Frontend/src/index.css
@@ -1,4 +1,25 @@
:root {
+ /* Light theme variables */
+ --background: #ffffff;
+ --foreground: #0a0a0a;
+ --card: #ffffff;
+ --card-foreground: #0a0a0a;
+ --popover: #ffffff;
+ --popover-foreground: #0a0a0a;
+ --primary: #0f172a;
+ --primary-foreground: #f8fafc;
+ --secondary: #f1f5f9;
+ --secondary-foreground: #0f172a;
+ --muted: #f1f5f9;
+ --muted-foreground: #64748b;
+ --accent: #f1f5f9;
+ --accent-foreground: #0f172a;
+ --destructive: #dc2626;
+ --destructive-foreground: #ffffff;
+ --border: #e2e8f0;
+ --input: #e2e8f0;
+ --ring: #94a3b8;
+
--vscode-background: #000000;
--vscode-foreground: #d4d4d4;
--vscode-activityBar-background: #333333;
@@ -18,6 +39,29 @@
--vscode-tab-border: #252526;
}
+.dark {
+ /* Dark theme variables */
+ --background: #000000;
+ --foreground: #ffffff;
+ --card: #1a1a1a;
+ --card-foreground: #e5e5e5;
+ --popover: #1a1a1a;
+ --popover-foreground: #e5e5e5;
+ --primary: #262626;
+ --primary-foreground: #ffffff;
+ --secondary: #1a1a1a;
+ --secondary-foreground: #ffffff;
+ --muted: #1a1a1a;
+ --muted-foreground: #a1a1a1;
+ --accent: #1a1a1a;
+ --accent-foreground: #ffffff;
+ --destructive: #991b1b;
+ --destructive-foreground: #ffffff;
+ --border: rgba(255, 255, 255, 0.1);
+ --input: rgba(255, 255, 255, 0.1);
+ --ring: #525252;
+}
+
body {
margin: 0;
padding: 0;
@@ -226,7 +270,7 @@ body {
flex-direction: column;
overflow: hidden;
height: 100%;
- width: calc(100% - ${sidebarVisible ? sidebarWidth : 0}px);
+ width: 100%;
transition: margin-left 0.2s ease, width 0.2s ease;
box-sizing: border-box;
}
@@ -1354,6 +1398,183 @@ body {
font-size: 14px;
}
+/* Login Page Styles */
+.login-container {
+ min-height: 100vh;
+ display: flex;
+ position: relative;
+ background-color: var(--background);
+ color: var(--foreground);
+}
+
+.login-left {
+ width: 50%;
+ position: relative;
+}
+
+.login-bg-image {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ filter: brightness(0.95);
+}
+
+.dark .login-bg-image {
+ filter: brightness(0.75);
+}
+
+.login-right {
+ width: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+ background-color: var(--background);
+}
+
+.login-form-container {
+ width: 100%;
+ max-width: 28rem;
+}
+
+.login-logos {
+ display: flex;
+ justify-content: center;
+ gap: 1rem;
+ margin-bottom: 2rem;
+}
+
+.login-logo {
+ height: 3rem;
+ width: auto;
+}
+
+.login-title {
+ font-size: 1.25rem;
+ font-weight: 600;
+ text-align: center;
+ color: var(--foreground);
+ font-family: monospace;
+ margin-bottom: 0.5rem;
+}
+
+.login-subtitle {
+ text-align: center;
+ font-size: 0.875rem;
+ color: var(--muted-foreground);
+ margin-bottom: 2rem;
+}
+
+.login-form {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.login-field {
+ display: flex;
+ flex-direction: column;
+}
+
+.login-label {
+ display: block;
+ font-size: 0.875rem;
+ font-weight: 500;
+ margin-bottom: 0.5rem;
+ color: var(--foreground);
+}
+
+.login-input {
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.375rem;
+ border: 1px solid var(--border);
+ background-color: var(--input);
+ color: var(--foreground);
+}
+
+.login-input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px #3b82f6;
+ border-color: transparent;
+}
+
+.login-error {
+ border: 1px solid rgba(239, 68, 68, 0.2);
+ background-color: rgba(239, 68, 68, 0.1);
+ color: #f87171;
+ padding: 0.75rem;
+ border-radius: 0.375rem;
+}
+
+.login-button {
+ width: 100%;
+ font-weight: 500;
+ padding: 0.5rem 1rem;
+ border-radius: 0.375rem;
+ transition: all 0.2s;
+ cursor: pointer;
+ border: none;
+ background-color: var(--primary);
+ color: var(--primary-foreground);
+}
+
+.login-button:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.login-footer {
+ margin-top: 1.5rem;
+ text-align: center;
+}
+
+.login-footer-text {
+ font-size: 0.875rem;
+ color: var(--muted-foreground);
+}
+
+.login-footer-link {
+ font-weight: 500;
+ color: var(--primary);
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.login-footer-link:hover {
+ text-decoration: underline;
+}
+
+.login-demo-note {
+ margin-top: 1.5rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid var(--border);
+}
+
+.login-demo-text {
+ font-size: 0.75rem;
+ text-align: center;
+ color: var(--muted-foreground);
+}
+
+.login-trust-logo {
+ margin-top: 2rem;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.trust-logo-img {
+ height: 2rem;
+ width: auto;
+}
+
+.theme-toggle {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ z-index: 10;
+}
+
/* Footer Styles */
.fixed {
position: fixed;