Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .vimrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
syntax-highlighting=True

3 changes: 3 additions & 0 deletions frontend/.env example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_SUPABASE_URL=YOUR SUPABASE URL
VITE_BASE_URL = http://localhost:5173/
VITE_SUPABASE_KEY=YOUR SUPABASE ANON KEY
3,122 changes: 1,435 additions & 1,687 deletions frontend/package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
"preview": "vite preview"
},
"dependencies": {

"@supabase/supabase-js": "^2.53.0",
"axios": "^1.8.3",
"date-fns": "^4.1.0",

"framer-motion": "^12.5.0",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
Expand Down
86 changes: 74 additions & 12 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
import { Toaster } from 'react-hot-toast';
import { AnimatePresence } from 'framer-motion';
import toast, { Toaster } from 'react-hot-toast';

import Sidebar from './components/layout/Sidebar';
import Dashboard from './components/dashboard/Dashboard';
import BotIntegrationPage from './components/integration/BotIntegrationPage';
Expand All @@ -12,29 +14,73 @@ import SupportPage from './components/pages/SupportPage';
import LandingPage from './components/landing/LandingPage';
import LoginPage from './components/pages/LoginPage';
import ProfilePage from './components/pages/ProfilePage';
import { AnimatePresence } from 'framer-motion';
import SignUpPage from './components/pages/SignUpPage';
import { supabase } from './lib/supabaseClient';
import ForgotPasswrdPage from './components/pages/ForgotPasswrdPage';
import ResetPasswordPage from './components/pages/ResetPasswordPage';

function App() {
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [repoData, setRepoData] = useState<any>(null); // Store fetched repo stats
const [repoData, setRepoData] = useState<any>(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);

// Check for existing authentication on app load
// Auto login if user has already logged in
useEffect(() => {
const savedAuth = localStorage.getItem('isAuthenticated');
if (savedAuth === 'true') {
setIsAuthenticated(true);
}
supabase.auth.getSession().then(({ data, error }) => {
if (error) {
toast.error('User Login Failed');
console.error('Error checking session:', error);
return;
}
setIsAuthenticated(!!data.session);
});

const { data: subscription } = supabase.auth.onAuthStateChange(
(event, session) => {
console.log("Auth event:", event, session);
switch (event) {
case "SIGNED_IN":
setIsAuthenticated(true);
toast.success("Signed in!");
break;

case "SIGNED_OUT":
setIsAuthenticated(false);
setRepoData(null);
toast.success("Signed out!");
break;

case "PASSWORD_RECOVERY":
toast("Check your email to reset your password.");
break;
case "TOKEN_REFRESHED":
console.log("Session refreshed");
break;
case "USER_UPDATED":
console.log("User updated", session?.user);
break;
}
}
);

return () => {
subscription.subscription.unsubscribe();
};
}, []);

const handleLogin = () => {
setIsAuthenticated(true);
localStorage.setItem('isAuthenticated', 'true');
};

const handleLogout = () => {
const handleLogout = async () => {
const { error } = await supabase.auth.signOut();
if (error) {
toast.error('Logout failed');
console.error('Error during logout:', error);
return;
}
toast.success('Signed out!');
setIsAuthenticated(false);
localStorage.removeItem('isAuthenticated');
setRepoData(null);
};

Expand Down Expand Up @@ -74,6 +120,23 @@ function App() {
)
}
/>
<Route
path="/forgot-password"
element={
isAuthenticated ? (
<Navigate to="/" replace />
) : (
<ForgotPasswrdPage />
)
}
/>
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route
path="/signup"
element={
isAuthenticated ? <Navigate to="/" replace /> : <SignUpPage />
}
/>
<Route
path="/"
element={
Expand All @@ -97,4 +160,3 @@ function App() {
}

export default App;

8 changes: 4 additions & 4 deletions frontend/src/components/landing/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import React, { useState } from 'react';
import { motion } from 'framer-motion';
import axios from 'axios';
import { toast } from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';

interface Props {
setRepoData: (data: any) => void; // Function to pass data to parent
setActivePage: (page: string) => void; // Function to navigate to other pages
}

const LandingPage: React.FC<Props> = ({ setRepoData, setActivePage }) => {
const LandingPage: React.FC<Props> = ({ setRepoData }) => {
const [repoUrl, setRepoUrl] = useState('');
const [loading, setLoading] = useState(false);

const navigate = useNavigate();
const fetchRepoStats = async () => {
if (!repoUrl) {
toast.error('Please enter a valid GitHub repository URL.');
Expand All @@ -23,7 +23,7 @@ const LandingPage: React.FC<Props> = ({ setRepoData, setActivePage }) => {
const response = await axios.post('http://localhost:8000/api/repo-stats', { repo_url: repoUrl });
setRepoData(response.data); // Pass fetched data to parent
toast.success('Repository stats fetched successfully!');
setActivePage('dashboard'); // Navigate to dashboard
navigate('/dashboard'); // Navigate to dashboard
} catch (error) {
toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
} finally {
Expand Down
153 changes: 153 additions & 0 deletions frontend/src/components/pages/ForgotPasswrdPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { useState, ReactNode, FormEvent } from "react";
import { motion } from "framer-motion";
import { useNavigate } from 'react-router-dom';
import { toast} from "react-hot-toast";
import { supabase } from "../../lib/supabaseClient";


import {
Settings,
Mail,
Lock,
} from 'lucide-react';

interface AuthLayoutProps {
children: ReactNode;
}

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


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 ForgotPasswrdPage() {
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [mailPage, setMailPage] = useState<boolean>(false);

const handleAuth = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = String(formData.get('email') || '').trim();
if (!email) {
toast.error("Please enter your email address.");
return;
}
setIsLoading(true);
try {
const base = import.meta.env.VITE_BASE_URL || window.location.origin;
const redirectTo = new URL('/reset-password', base).toString();
const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo });
if (error) {
console.error('resetPasswordForEmail failed', error);
}
setMailPage(true);
} catch (err) {
console.error('resetPasswordForEmail unexpected error', err);
toast.error("Something went wrong. Please try again.");
} finally {
setIsLoading(false);
}
};

return (
<AuthLayout>

<div className="bg-gray-900 p-8 rounded-xl border border-gray-800">
{!mailPage ? (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center mb-8"
>
<h1 className="text-3xl font-bold text-white mb-2">Account Recovery</h1>
<p className="text-gray-400">Reset your password</p>
</motion.div>
<form onSubmit={handleAuth} className="space-y-6">
<InputField
icon={Mail}
type="email"
name="email"
className="mb-7"
placeholder="Email address"
required
/>
<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>
) : (
<>
<Lock size={20} className="mr-2" />
Reset Password
</>
)}
</motion.button>
<p className="text-center text-gray-400 text-sm">
<button
type="button"
onClick={() => navigate('/login')}
className="text-gray-400 hover:text-gray-300 font-medium"
>
Back to Sign In
</button>
</p>
</form>
</>
) : (
<div className="flex flex-col items-center justify-center py-12">
<Mail className="w-16 h-16 text-green-500 mb-4" />
<h2 className="text-2xl font-bold text-white mb-2">Check your inbox</h2>
<p className="text-gray-400 mb-4 text-center">
We've sent a password reset email to this address. Please check your inbox and follow the instructions to reset your password.
</p>
<button
type="button"
onClick={() => { setMailPage(false); navigate('/login') }}
className="mt-4 px-6 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition-colors"
>
Back to Sign In
</button>
</div>
)}
</div>
</AuthLayout>
);
}
28 changes: 20 additions & 8 deletions frontend/src/components/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, ReactNode, FormEvent } from "react";
import { motion } from "framer-motion";
import { useNavigate } from 'react-router-dom';
import { toast } from "react-hot-toast";
import { supabase } from "../../lib/supabaseClient";
import {
Settings,
Mail,
Expand Down Expand Up @@ -52,15 +53,24 @@ export default function LoginPage({ onLogin }: LoginPageProps) {

const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
const password = formData.get('password') as string;
setIsLoading(true);

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

const {data,error} = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
setIsLoading(false);
toast.success('Successfully logged in!');
onLogin();
navigate('/');
if(data && !error){
toast.success('Successfully logged in!');
onLogin();
navigate('/');
}
else
{
toast.error(error?.message ||"An Unknown error occured!");
}
};

return (
Expand All @@ -80,12 +90,14 @@ export default function LoginPage({ onLogin }: LoginPageProps) {
<InputField
icon={Mail}
type="email"
name="email"
placeholder="Email address"
required
/>
<InputField
icon={Lock}
type="password"
name="password"
placeholder="Password"
required
/>
Expand All @@ -98,7 +110,7 @@ export default function LoginPage({ onLogin }: LoginPageProps) {
</label>
<button
type="button"
onClick={() => toast.success('Reset link sent!')}
onClick={() => navigate('/forgot-password')}
className="text-green-400 hover:text-green-300"
>
Forgot password?
Expand Down
Loading