Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
23 changes: 16 additions & 7 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,30 @@ 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';

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 }) => {
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 +81,14 @@ function App() {
)
}
/>
<Route
path="/signup"
element= {isAuthenticated ? (
<Navigate to="/" replace />
) : (
<SignUpPage/>
)}
/>
<Route
path="/*"
element={
Expand Down
26 changes: 19 additions & 7 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 Down
203 changes: 203 additions & 0 deletions frontend/src/components/pages/SignUpPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
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,
LogIn
} from 'lucide-react';

interface AuthLayoutProps {
children: ReactNode;
}

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

interface LoginPageProps {
onLogin: () => void;
}



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

const handleSignUp = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
const name = formData.get('name') as string;
const password = formData.get('password') as string;
setIsLoading(true);
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
options: {
data: {
display_name: name
},
emailRedirectTo: 'http://localhost:5173/',
},
})
setIsLoading(false);
// Email already confirmed
if (data?.user?.confirmed_at) {
toast.error("Email is already registered. Please log in.");
return;
}
if (error) {
toast.error(error.message || "An Unknown error occured!");
return;
}
setMailPage(true);
};

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">Welcome to Devr.AI</h1>
<p className="text-gray-400">Create a new account</p>
</motion.div>
<form onSubmit={handleSignUp} className="space-y-6">
<div className="space-y-4">
<InputField
icon={Mail}
name="name"
placeholder="Username"
required
/>
<InputField
icon={Mail}
type="email"
name="email"
placeholder="Email address"
required
/>
<InputField
icon={Lock}
type="password"
name="password"
placeholder="Password"
required
/>
<InputField
icon={Lock}
type="password"
name="repassword"
placeholder="Reenter Password"
required
/>
</div>

<div className="flex items-center justify-between text-sm">
<label className="flex items-center">
<input type="checkbox" className="rounded bg-gray-800 border-gray-700 text-green-500 focus:ring-green-500" />
<span className="ml-2 text-gray-300">Remember me</span>
</label>
<button
type="button"
onClick={() => toast.success('Reset link sent!')}
className="text-green-400 hover:text-green-300"
>
Forgot password?
</button>
</div>

<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>
) : (
<>
<LogIn size={20} className="mr-2" />
Sign Up
</>
)}
</motion.button>

<p className="text-center text-gray-400 text-sm">
Already have an Account?{' '}
<button
type="button"
onClick={() => navigate('/login')}
className="text-green-400 hover:text-green-300 font-medium"
>
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 confirmation email to your address. Please check your inbox and follow the instructions to complete your registration.
</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>
);
}
8 changes: 8 additions & 0 deletions frontend/src/lib/supabaseClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createClient } from '@supabase/supabase-js'


const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || "";
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_KEY || "";


export const supabase = createClient(supabaseUrl, supabaseAnonKey)