Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.5.2",
"react-router-dom": "^7.5.0",
"recharts": "^2.15.1",
"tailwind-merge": "^3.0.2"
},
Expand Down
67 changes: 45 additions & 22 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';

import Sidebar from './components/layout/Sidebar';
import Dashboard from './components/dashboard/Dashboard';


import BotIntegrationPage from './components/integration/BotIntegrationPage';
import ContributorsPage from './components/contributors/ContributorsPage';
import PullRequestsPage from './components/pages/PullRequestsPage';
Expand Down Expand Up @@ -42,26 +43,48 @@ function App() {
};

return (
<div className="min-h-screen bg-gray-950 text-white">

{activePage !== 'landing' && (
<Sidebar
isOpen={isSidebarOpen}
setIsOpen={setIsSidebarOpen}
activePage={activePage}
setActivePage={setActivePage}
/>
)}

<main className={`transition-all duration-300 ${isSidebarOpen && activePage !== 'landing' ? 'ml-64' : 'ml-20'}`}>

<div className="p-8">
<AnimatePresence mode="wait">
{renderPage()}
</AnimatePresence>
</div>
</main>
</div>
<Router>
<div className="min-h-screen bg-gray-950 text-white">
<Routes>
<Route
path="/login"
element={
isAuthenticated ? (
<Navigate to="/" replace />
) : (
<LoginPage onLogin={handleLogin} />
)
}
/>
<Route
path="/*"
element={
isAuthenticated ? (
<div className="flex">
<Sidebar
isOpen={isSidebarOpen}
setIsOpen={setIsSidebarOpen}
activePage={activePage}
setActivePage={setActivePage}
/>
<main
className={`transition-all duration-300 flex-1 ${
isSidebarOpen ? 'ml-64' : 'ml-20'
}`}
>
<div className="p-8">
<AnimatePresence mode="wait">{renderPage()}</AnimatePresence>
</div>
</main>
</div>
) : (
<Navigate to="/login" replace />
)
}
/>
</Routes>
</div>
</Router>
);
}

Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
GitPullRequest,
MessageCircleQuestion,
Menu,
Settings
Settings,
User
} from 'lucide-react';

interface SidebarProps {
Expand Down Expand Up @@ -37,6 +38,7 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, setIsOpen, activePage, setAct
{ icon: <GitPullRequest size={20} />, label: 'Pull Requests', id: 'prs' },
{ icon: <MessageCircleQuestion size={20} />, label: 'Support', id: 'support' },
{ icon: <Settings size={20} />, label: 'Settings', id: 'settings' },
{ icon: <User size={20} />, label: 'Profile', id: 'profile' },
].map((item) => (
<button
key={item.id}
Expand Down
144 changes: 144 additions & 0 deletions frontend/src/components/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { useState, ReactNode, FormEvent } from "react";
import { motion } from "framer-motion";
import { useNavigate } from 'react-router-dom';
import { toast } from "react-hot-toast";
import {
Settings,
Mail,
Lock,
LogIn
} from 'lucide-react';

interface AuthLayoutProps {
children: ReactNode;
}

interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
icon: React.ElementType;
}

interface LoginPageProps {
onLogin: () => void;
}

const AuthLayout = ({ children }: AuthLayoutProps) => (
<div className="min-h-screen bg-gray-950 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="w-full max-w-md"
>
{children}
</motion.div>
</div>
);

const InputField = ({ icon: Icon, ...props }: InputFieldProps) => (
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Icon className="h-5 w-5 text-gray-400" />
</div>
<input
{...props}
className="block w-full pl-10 pr-3 py-2 border border-gray-800 rounded-lg bg-gray-900 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent"
/>
</div>
);

export default function LoginPage({ onLogin }: LoginPageProps) {
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState<boolean>(false);

const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsLoading(true);

// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));

setIsLoading(false);
toast.success('Successfully logged in!');
onLogin();
navigate('/');
};

return (
<AuthLayout>
<div className="bg-gray-900 p-8 rounded-xl border border-gray-800">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center mb-8"
>
<h1 className="text-3xl font-bold text-white mb-2">Welcome back</h1>
<p className="text-gray-400">Sign in to your account</p>
</motion.div>

<form onSubmit={handleLogin} className="space-y-6">
<div className="space-y-4">
<InputField
icon={Mail}
type="email"
placeholder="Email address"
required
/>
<InputField
icon={Lock}
type="password"
placeholder="Password"
required
/>
</div>

<div className="flex items-center justify-between text-sm">
<label className="flex items-center">
<input type="checkbox" className="rounded bg-gray-800 border-gray-700 text-green-500 focus:ring-green-500" />
<span className="ml-2 text-gray-300">Remember me</span>
</label>
<button
type="button"
onClick={() => toast.success('Reset link sent!')}
className="text-green-400 hover:text-green-300"
>
Forgot password?
</button>
</div>

<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
type="submit"
disabled={isLoading}
className="w-full py-3 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition-colors flex items-center justify-center"
>
{isLoading ? (
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
>
<Settings size={20} />
</motion.div>
) : (
<>
<LogIn size={20} className="mr-2" />
Sign In
</>
)}
</motion.button>

<p className="text-center text-gray-400 text-sm">
Don't have an account?{' '}
<button
type="button"
onClick={() => navigate('/signup')}
className="text-green-400 hover:text-green-300 font-medium"
>
Sign up
</button>
</p>
</form>
</div>
</AuthLayout>
);
}
Loading
Loading