Skip to content
Closed
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
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) => {
Comment on lines +38 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Simplify subscription handling and make cleanup safe.

Current cleanup may throw if subscription is undefined; also double β€œsubscription.subscription” is awkward.

-    const { data: subscription } = supabase.auth.onAuthStateChange(
+    const { data: { subscription } } = supabase.auth.onAuthStateChange(
       (event, session) => {
         // ...
       }
     );
-    return () => {
-      subscription.subscription.unsubscribe();
-    };
+    return () => subscription.unsubscribe();

Also applies to: 66-68

πŸ€– Prompt for AI Agents
In frontend/src/App.tsx around lines 38-39 (and similarly 66-68), the code
assigns const { data: subscription } = supabase.auth.onAuthStateChange(...) then
later calls subscription.subscription which is awkward and the cleanup can throw
if subscription is undefined; change to capture the return value directly (e.g.,
const subscription = supabase.auth.onAuthStateChange(...)) and in the cleanup
use optional chaining or a null-check to call subscription.unsubscribe() only if
the subscription exists (or return a cleanup function that calls
subscription?.unsubscribe()). This removes the double
β€œsubscription.subscription” and makes teardown safe.

console.log("Auth event:", event, session);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not log the full session object (risk of token leakage).

Access/refresh tokens can end up in browser logs. Log the event only (or at most session?.user.id).

-        console.log("Auth event:", event, session);
+        console.log("Auth event:", event);
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log("Auth event:", event, session);
console.log("Auth event:", event);
πŸ€– Prompt for AI Agents
In frontend/src/App.tsx around line 40, the current console.log prints the full
session object which may expose tokens; remove or replace that log so it does
not emit the session contents. Log only the event (e.g., console.log("Auth
event:", event)) or, if you need an identifier, log session?.user?.id or other
non-sensitive fields; ensure you do not include session.access_token,
session.refresh_token, or any token-like fields in logs.

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 {
Comment on lines 23 to 29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Pass auth token, add timeout, and route 401s to login

Improves security, resilience, and UX.

-      const response = await axios.post('http://localhost:8000/api/repo-stats', { repo_url: repoUrl });
+      const { data: { session } } = await supabase.auth.getSession();
+      const response = await axios.post(
+        `${API_BASE}/api/repo-stats`,
+        { repo_url: repoUrl },
+        {
+          headers: session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : undefined,
+          timeout: 15000,
+        }
+      );
       setRepoData(response.data); // Pass fetched data to parent
       toast.success('Repository stats fetched successfully!');
       navigate('/dashboard'); // Navigate to dashboard
-    } catch (error) {
-      toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        if (error.response?.status === 401) {
+          toast.error('Please log in to analyze repositories.');
+          navigate('/login');
+          return;
+        }
+        const msg = error.response?.data?.message ?? error.message;
+        toast.error(`Failed to fetch repository stats: ${msg}`);
+      } else {
+        toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
+      }
     } finally {
       setLoading(false);
     }

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In frontend/src/components/landing/LandingPage.tsx around lines 23 to 29, the
axios POST call should include the user's auth token, a request timeout, and
explicit handling of 401 responses; change the request to send headers: {
Authorization: `Bearer ${token}` } (obtain token from localStorage or auth
context) and add a timeout option (e.g., 10000 ms), then in the catch block
inspect the error response status: if 401 navigate('/login') and show an
appropriate toast, otherwise show the existing generic failure toast; keep
finally behavior unchanged.

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