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
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
156 changes: 156 additions & 0 deletions frontend/src/components/pages/ForgotPasswrdPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
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);
toast.error(error.message || 'Could not send reset email.');
return;
}
toast.success('Password reset email sent.');
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