Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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_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
48 changes: 40 additions & 8 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import { Toaster } from 'react-hot-toast';
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 @@ -13,29 +13,37 @@ 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 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 [activePage, setActivePage] = useState('landing'); // Default to landing page
const [repoData, setRepoData] = useState<any>(null); // Store fetched repo stats
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;
}
if (data.session) {
setIsAuthenticated(true);
}
});
}, []);

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

const handleLogout = () => {
setIsAuthenticated(false);
localStorage.removeItem('isAuthenticated');
setActivePage('landing');
setRepoData(null);
};
Expand Down Expand Up @@ -80,6 +88,30 @@ 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 Down
145 changes: 145 additions & 0 deletions frontend/src/components/pages/ForgotPasswrdPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useState, ReactNode, FormEvent } from "react";
import { motion } from "framer-motion";
import { useNavigate } from 'react-router-dom';
import { toast, Toaster } 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 = formData.get('email') as string;
setIsLoading(true);
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: import.meta.env.VITE_BASE_URL+'reset-password',
});
setIsLoading(false);
if (error) {
toast.error(error.message || "An unknown error occurred!");
return;
}
setMailPage(true);
};
Comment on lines +54 to +68
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

Harden reset flow: robust redirect URL join, avoid account enumeration, and always clear loading

  • Join URLs safely and fall back to window.location.origin to avoid broken links when VITE_BASE_URL lacks a trailing slash.
  • Don’t surface raw auth errors (prevents user enumeration). Show a generic success message regardless, and log errors internally.
  • Use try/catch/finally so isLoading is reset on unexpected failures.
   const handleAuth = async (e: FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     const formData = new FormData(e.currentTarget);
-    const email = formData.get('email') as string;
-    setIsLoading(true);
-    const { error } = await supabase.auth.resetPasswordForEmail(email, {
-      redirectTo: import.meta.env.VITE_BASE_URL+'reset-password',
-    });
-    setIsLoading(false);
-    if (error) {
-      toast.error(error.message || "An unknown error occurred!");
-      return;
-    }
-    setMailPage(true);
+    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 });
+      // Avoid account enumeration: do not expose specific error details to users.
+      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);
+    }
   };
πŸ“ 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
const handleAuth = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
setIsLoading(true);
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: import.meta.env.VITE_BASE_URL+'reset-password',
});
setIsLoading(false);
if (error) {
toast.error(error.message || "An unknown error occurred!");
return;
}
setMailPage(true);
};
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 });
// Avoid account enumeration: do not expose specific error details to users.
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">
<Toaster position="top-right" />
{!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('/signup')}
Copy link
Contributor

Choose a reason for hiding this comment

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

this should re-direct to sign in. It says "Back to SignIn" but redirect to SignUp

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