Skip to content

Commit eba8112

Browse files
Merge pull request #132 from PinJinx/AuthPageNew
Implementaion of fully functional Authentication System for frontend
2 parents f31ba33 + 26b55e7 commit eba8112

File tree

11 files changed

+2135
-1715
lines changed

11 files changed

+2135
-1715
lines changed

frontend/.env example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
VITE_SUPABASE_URL=YOUR SUPABASE URL
2+
VITE_BASE_URL = http://localhost:5173/
3+
VITE_SUPABASE_KEY=YOUR SUPABASE ANON KEY

frontend/package-lock.json

Lines changed: 1435 additions & 1687 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13-
13+
"@supabase/supabase-js": "^2.53.0",
1414
"axios": "^1.8.3",
1515
"date-fns": "^4.1.0",
16-
1716
"framer-motion": "^12.5.0",
1817
"lucide-react": "^0.344.0",
1918
"react": "^18.3.1",

frontend/src/App.tsx

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { useEffect, useState } from 'react';
22
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
3-
import { Toaster } from 'react-hot-toast';
3+
import { AnimatePresence } from 'framer-motion';
4+
import toast, { Toaster } from 'react-hot-toast';
5+
46
import Sidebar from './components/layout/Sidebar';
57
import Dashboard from './components/dashboard/Dashboard';
68
import BotIntegrationPage from './components/integration/BotIntegrationPage';
@@ -12,29 +14,73 @@ import SupportPage from './components/pages/SupportPage';
1214
import LandingPage from './components/landing/LandingPage';
1315
import LoginPage from './components/pages/LoginPage';
1416
import ProfilePage from './components/pages/ProfilePage';
15-
import { AnimatePresence } from 'framer-motion';
17+
import SignUpPage from './components/pages/SignUpPage';
18+
import { supabase } from './lib/supabaseClient';
19+
import ForgotPasswrdPage from './components/pages/ForgotPasswrdPage';
20+
import ResetPasswordPage from './components/pages/ResetPasswordPage';
1621

1722
function App() {
1823
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
19-
const [repoData, setRepoData] = useState<any>(null); // Store fetched repo stats
24+
const [repoData, setRepoData] = useState<any>(null);
2025
const [isAuthenticated, setIsAuthenticated] = useState(false);
2126

22-
// Check for existing authentication on app load
27+
// Auto login if user has already logged in
2328
useEffect(() => {
24-
const savedAuth = localStorage.getItem('isAuthenticated');
25-
if (savedAuth === 'true') {
26-
setIsAuthenticated(true);
27-
}
29+
supabase.auth.getSession().then(({ data, error }) => {
30+
if (error) {
31+
toast.error('User Login Failed');
32+
console.error('Error checking session:', error);
33+
return;
34+
}
35+
setIsAuthenticated(!!data.session);
36+
});
37+
38+
const { data: subscription } = supabase.auth.onAuthStateChange(
39+
(event, session) => {
40+
console.log("Auth event:", event, session);
41+
switch (event) {
42+
case "SIGNED_IN":
43+
setIsAuthenticated(true);
44+
toast.success("Signed in!");
45+
break;
46+
47+
case "SIGNED_OUT":
48+
setIsAuthenticated(false);
49+
setRepoData(null);
50+
toast.success("Signed out!");
51+
break;
52+
53+
case "PASSWORD_RECOVERY":
54+
toast("Check your email to reset your password.");
55+
break;
56+
case "TOKEN_REFRESHED":
57+
console.log("Session refreshed");
58+
break;
59+
case "USER_UPDATED":
60+
console.log("User updated", session?.user);
61+
break;
62+
}
63+
}
64+
);
65+
66+
return () => {
67+
subscription.subscription.unsubscribe();
68+
};
2869
}, []);
2970

3071
const handleLogin = () => {
3172
setIsAuthenticated(true);
32-
localStorage.setItem('isAuthenticated', 'true');
3373
};
3474

35-
const handleLogout = () => {
75+
const handleLogout = async () => {
76+
const { error } = await supabase.auth.signOut();
77+
if (error) {
78+
toast.error('Logout failed');
79+
console.error('Error during logout:', error);
80+
return;
81+
}
82+
toast.success('Signed out!');
3683
setIsAuthenticated(false);
37-
localStorage.removeItem('isAuthenticated');
3884
setRepoData(null);
3985
};
4086

@@ -74,6 +120,23 @@ function App() {
74120
)
75121
}
76122
/>
123+
<Route
124+
path="/forgot-password"
125+
element={
126+
isAuthenticated ? (
127+
<Navigate to="/" replace />
128+
) : (
129+
<ForgotPasswrdPage />
130+
)
131+
}
132+
/>
133+
<Route path="/reset-password" element={<ResetPasswordPage />} />
134+
<Route
135+
path="/signup"
136+
element={
137+
isAuthenticated ? <Navigate to="/" replace /> : <SignUpPage />
138+
}
139+
/>
77140
<Route
78141
path="/"
79142
element={
@@ -97,4 +160,3 @@ function App() {
97160
}
98161

99162
export default App;
100-

frontend/src/components/landing/LandingPage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import React, { useState } from 'react';
22
import { motion } from 'framer-motion';
33
import axios from 'axios';
44
import { toast } from 'react-hot-toast';
5+
import { useNavigate } from 'react-router-dom';
56

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

11-
const LandingPage: React.FC<Props> = ({ setRepoData, setActivePage }) => {
11+
const LandingPage: React.FC<Props> = ({ setRepoData }) => {
1212
const [repoUrl, setRepoUrl] = useState('');
1313
const [loading, setLoading] = useState(false);
14-
14+
const navigate = useNavigate();
1515
const fetchRepoStats = async () => {
1616
if (!repoUrl) {
1717
toast.error('Please enter a valid GitHub repository URL.');
@@ -23,7 +23,7 @@ const LandingPage: React.FC<Props> = ({ setRepoData, setActivePage }) => {
2323
const response = await axios.post('http://localhost:8000/api/repo-stats', { repo_url: repoUrl });
2424
setRepoData(response.data); // Pass fetched data to parent
2525
toast.success('Repository stats fetched successfully!');
26-
setActivePage('dashboard'); // Navigate to dashboard
26+
navigate('/dashboard'); // Navigate to dashboard
2727
} catch (error) {
2828
toast.error('Failed to fetch repository stats. Please check the URL or backend server.');
2929
} finally {
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { useState, ReactNode, FormEvent } from "react";
2+
import { motion } from "framer-motion";
3+
import { useNavigate } from 'react-router-dom';
4+
import { toast} from "react-hot-toast";
5+
import { supabase } from "../../lib/supabaseClient";
6+
7+
8+
import {
9+
Settings,
10+
Mail,
11+
Lock,
12+
} from 'lucide-react';
13+
14+
interface AuthLayoutProps {
15+
children: ReactNode;
16+
}
17+
18+
interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
19+
icon: React.ElementType;
20+
}
21+
22+
23+
const AuthLayout = ({ children }: AuthLayoutProps) => (
24+
<div className="min-h-screen bg-gray-950 flex items-center justify-center p-4">
25+
<motion.div
26+
initial={{ opacity: 0, y: 20 }}
27+
animate={{ opacity: 1, y: 0 }}
28+
exit={{ opacity: 0, y: -20 }}
29+
className="w-full max-w-md"
30+
>
31+
{children}
32+
</motion.div>
33+
</div>
34+
);
35+
36+
const InputField = ({ icon: Icon, ...props }: InputFieldProps) => (
37+
<div className="relative">
38+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
39+
<Icon className="h-5 w-5 text-gray-400" />
40+
</div>
41+
<input
42+
{...props}
43+
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"
44+
/>
45+
</div>
46+
);
47+
48+
49+
export default function ForgotPasswrdPage() {
50+
const navigate = useNavigate();
51+
const [isLoading, setIsLoading] = useState<boolean>(false);
52+
const [mailPage, setMailPage] = useState<boolean>(false);
53+
54+
const handleAuth = async (e: FormEvent<HTMLFormElement>) => {
55+
e.preventDefault();
56+
const formData = new FormData(e.currentTarget);
57+
const email = String(formData.get('email') || '').trim();
58+
if (!email) {
59+
toast.error("Please enter your email address.");
60+
return;
61+
}
62+
setIsLoading(true);
63+
try {
64+
const base = import.meta.env.VITE_BASE_URL || window.location.origin;
65+
const redirectTo = new URL('/reset-password', base).toString();
66+
const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo });
67+
if (error) {
68+
console.error('resetPasswordForEmail failed', error);
69+
toast.error(error.message || 'Could not send reset email.');
70+
return;
71+
}
72+
toast.success('Password reset email sent.');
73+
setMailPage(true);
74+
} catch (err) {
75+
console.error('resetPasswordForEmail unexpected error', err);
76+
toast.error("Something went wrong. Please try again.");
77+
} finally {
78+
setIsLoading(false);
79+
}
80+
};
81+
82+
return (
83+
<AuthLayout>
84+
85+
<div className="bg-gray-900 p-8 rounded-xl border border-gray-800">
86+
{!mailPage ? (
87+
<>
88+
<motion.div
89+
initial={{ opacity: 0 }}
90+
animate={{ opacity: 1 }}
91+
className="text-center mb-8"
92+
>
93+
<h1 className="text-3xl font-bold text-white mb-2">Account Recovery</h1>
94+
<p className="text-gray-400">Reset your password</p>
95+
</motion.div>
96+
<form onSubmit={handleAuth} className="space-y-6">
97+
<InputField
98+
icon={Mail}
99+
type="email"
100+
name="email"
101+
className="mb-7"
102+
placeholder="Email address"
103+
required
104+
/>
105+
<motion.button
106+
whileHover={{ scale: 1.02 }}
107+
whileTap={{ scale: 0.98 }}
108+
type="submit"
109+
disabled={isLoading}
110+
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"
111+
>
112+
{isLoading ? (
113+
<motion.div
114+
animate={{ rotate: 360 }}
115+
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
116+
>
117+
<Settings size={20} />
118+
</motion.div>
119+
) : (
120+
<>
121+
<Lock size={20} className="mr-2" />
122+
Reset Password
123+
</>
124+
)}
125+
</motion.button>
126+
<p className="text-center text-gray-400 text-sm">
127+
<button
128+
type="button"
129+
onClick={() => navigate('/login')}
130+
className="text-gray-400 hover:text-gray-300 font-medium"
131+
>
132+
Back to Sign In
133+
</button>
134+
</p>
135+
</form>
136+
</>
137+
) : (
138+
<div className="flex flex-col items-center justify-center py-12">
139+
<Mail className="w-16 h-16 text-green-500 mb-4" />
140+
<h2 className="text-2xl font-bold text-white mb-2">Check your inbox</h2>
141+
<p className="text-gray-400 mb-4 text-center">
142+
We've sent a password reset email to this address. Please check your inbox and follow the instructions to reset your password.
143+
</p>
144+
<button
145+
type="button"
146+
onClick={() => { setMailPage(false); navigate('/login') }}
147+
className="mt-4 px-6 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg font-medium transition-colors"
148+
>
149+
Back to Sign In
150+
</button>
151+
</div>
152+
)}
153+
</div>
154+
</AuthLayout>
155+
);
156+
}

frontend/src/components/pages/LoginPage.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, ReactNode, FormEvent } from "react";
22
import { motion } from "framer-motion";
33
import { useNavigate } from 'react-router-dom';
44
import { toast } from "react-hot-toast";
5+
import { supabase } from "../../lib/supabaseClient";
56
import {
67
Settings,
78
Mail,
@@ -52,15 +53,24 @@ export default function LoginPage({ onLogin }: LoginPageProps) {
5253

5354
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
5455
e.preventDefault();
56+
const formData = new FormData(e.currentTarget);
57+
const email = formData.get('email') as string;
58+
const password = formData.get('password') as string;
5559
setIsLoading(true);
56-
57-
// Simulate API call
58-
await new Promise(resolve => setTimeout(resolve, 1500));
59-
60+
const {data,error} = await supabase.auth.signInWithPassword({
61+
email: email,
62+
password: password,
63+
});
6064
setIsLoading(false);
61-
toast.success('Successfully logged in!');
62-
onLogin();
63-
navigate('/');
65+
if(data && !error){
66+
toast.success('Successfully logged in!');
67+
onLogin();
68+
navigate('/');
69+
}
70+
else
71+
{
72+
toast.error(error?.message ||"An Unknown error occured!");
73+
}
6474
};
6575

6676
return (
@@ -80,12 +90,14 @@ export default function LoginPage({ onLogin }: LoginPageProps) {
8090
<InputField
8191
icon={Mail}
8292
type="email"
93+
name="email"
8394
placeholder="Email address"
8495
required
8596
/>
8697
<InputField
8798
icon={Lock}
8899
type="password"
100+
name="password"
89101
placeholder="Password"
90102
required
91103
/>
@@ -98,7 +110,7 @@ export default function LoginPage({ onLogin }: LoginPageProps) {
98110
</label>
99111
<button
100112
type="button"
101-
onClick={() => toast.success('Reset link sent!')}
113+
onClick={() => navigate('/forgot-password')}
102114
className="text-green-400 hover:text-green-300"
103115
>
104116
Forgot password?

0 commit comments

Comments
 (0)