Run SQL migrations

The SQL migrations have been reviewed and approved, and are now being run.
This commit is contained in:
gpt-engineer-app[bot] 2025-03-09 11:56:33 +00:00
parent 5a113a2b26
commit 58c08af3e8
7 changed files with 519 additions and 21 deletions

View File

@ -1,26 +1,61 @@
import { Toaster } from "@/components/ui/toaster"; import { Toaster } from "@/components/ui/toaster";
import { Toaster as Sonner } from "@/components/ui/sonner"; import { Toaster as Sonner } from "@/components/ui/sonner";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
import Index from "./pages/Index"; import Index from "./pages/Index";
import Auth from "./pages/Auth";
import NotFound from "./pages/NotFound"; import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
// Protected route component
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { user, loading } = useAuth();
if (loading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
if (!user) {
return <Navigate to="/auth" />;
}
return <>{children}</>;
};
const App = () => ( const App = () => (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<TooltipProvider> <AuthProvider>
<Toaster /> <TooltipProvider>
<Sonner /> <Toaster />
<BrowserRouter> <Sonner />
<Routes> <BrowserRouter>
<Route path="/" element={<Index />} /> <Routes>
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} <Route path="/" element={<Index />} />
<Route path="*" element={<NotFound />} /> <Route path="/auth" element={<Auth />} />
</Routes> {/* Protected routes example (Dashboard will be implemented later) */}
</BrowserRouter> <Route
</TooltipProvider> path="/dashboard"
element={
<ProtectedRoute>
<div className="min-h-screen pt-20 px-4">
<div className="container mx-auto">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<p>This is a protected route. Dashboard implementation coming soon.</p>
</div>
</div>
</ProtectedRoute>
}
/>
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider> </QueryClientProvider>
); );

View File

@ -2,13 +2,24 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ShieldCheck, Menu, X } from 'lucide-react'; import { ShieldCheck, Menu, X, User, LogOut } from 'lucide-react';
import { useAuth } from '@/contexts/AuthContext';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
const Header = () => { const Header = () => {
const [isScrolled, setIsScrolled] = useState(false); const [isScrolled, setIsScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const location = useLocation(); const location = useLocation();
const isHome = location.pathname === '/'; const isHome = location.pathname === '/';
const { user, profile, signOut } = useAuth();
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
@ -18,6 +29,17 @@ const Header = () => {
return () => window.removeEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll);
}, []); }, []);
const getInitials = () => {
if (profile?.full_name) {
return profile.full_name
.split(' ')
.map((n: string) => n[0])
.join('')
.toUpperCase();
}
return user?.email?.substring(0, 2).toUpperCase() || 'U';
};
return ( return (
<header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-lg shadow-sm' : 'bg-transparent'}`}> <header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled ? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-lg shadow-sm' : 'bg-transparent'}`}>
<div className="container mx-auto px-4 sm:px-6 lg:px-8"> <div className="container mx-auto px-4 sm:px-6 lg:px-8">
@ -46,13 +68,38 @@ const Header = () => {
{/* Desktop CTA */} {/* Desktop CTA */}
<div className="hidden md:flex items-center space-x-4"> <div className="hidden md:flex items-center space-x-4">
{isHome ? ( {user ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarFallback>{getInitials()}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link to="/dashboard">Dashboard</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to="/profile">Profile</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()} className="text-destructive">
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : isHome ? (
<Button asChild variant="default" size="sm" className="rounded-full px-6 shadow-md hover:shadow-lg transition-all"> <Button asChild variant="default" size="sm" className="rounded-full px-6 shadow-md hover:shadow-lg transition-all">
<Link to="/dashboard">Try for Free</Link> <Link to="/auth">Try for Free</Link>
</Button> </Button>
) : ( ) : (
<Button variant="ghost" size="sm" className="rounded-full"> <Button asChild variant="ghost" size="sm" className="rounded-full">
Need Help? <Link to="/auth">Sign In</Link>
</Button> </Button>
)} )}
</div> </div>
@ -80,9 +127,28 @@ const Header = () => {
<a href="#pricing" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}> <a href="#pricing" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
Pricing Pricing
</a> </a>
{isHome && ( {user ? (
<>
<Link to="/profile" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
Profile
</Link>
<Button
variant="outline"
className="w-full mt-3"
onClick={() => {
signOut();
setMobileMenuOpen(false);
}}
>
<LogOut className="mr-2 h-4 w-4" />
Log out
</Button>
</>
) : (
<Button asChild className="w-full mt-3 rounded-full" size="sm"> <Button asChild className="w-full mt-3 rounded-full" size="sm">
<Link to="/dashboard" onClick={() => setMobileMenuOpen(false)}>Try for Free</Link> <Link to="/auth" onClick={() => setMobileMenuOpen(false)}>
{isHome ? 'Try for Free' : 'Sign In'}
</Link>
</Button> </Button>
)} )}
</div> </div>

View File

@ -1,3 +1,4 @@
import * as React from "react" import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar" import * as AvatarPrimitive from "@radix-ui/react-avatar"

View File

@ -1,3 +1,4 @@
import * as React from "react" import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react"
@ -81,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}

View File

@ -0,0 +1,175 @@
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { Session, User } from '@supabase/supabase-js';
import { useToast } from '@/hooks/use-toast';
type AuthContextType = {
session: Session | null;
user: User | null;
profile: any | null;
loading: boolean;
signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string, fullName: string) => Promise<void>;
signOut: () => Promise<void>;
};
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [session, setSession] = useState<Session | null>(null);
const [user, setUser] = useState<User | null>(null);
const [profile, setProfile] = useState<any | null>(null);
const [loading, setLoading] = useState(true);
const { toast } = useToast();
useEffect(() => {
// Get the initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
if (session?.user) {
fetchProfile(session.user.id);
}
setLoading(false);
});
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session);
setUser(session?.user ?? null);
if (session?.user) {
fetchProfile(session.user.id);
} else {
setProfile(null);
}
setLoading(false);
}
);
return () => {
subscription.unsubscribe();
};
}, []);
const fetchProfile = async (userId: string) => {
try {
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single();
if (error) {
console.error('Error fetching profile:', error);
return;
}
setProfile(data);
} catch (error) {
console.error('Error fetching profile:', error);
}
};
const signIn = async (email: string, password: string) => {
try {
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
toast({
title: "Success!",
description: "You are now signed in.",
});
} catch (error: any) {
toast({
title: "Error signing in",
description: error.message,
variant: "destructive",
});
console.error('Error signing in:', error);
throw error;
} finally {
setLoading(false);
}
};
const signUp = async (email: string, password: string, fullName: string) => {
try {
setLoading(true);
const { error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
full_name: fullName,
}
}
});
if (error) throw error;
toast({
title: "Account created!",
description: "Please check your email to confirm your account.",
});
} catch (error: any) {
toast({
title: "Error creating account",
description: error.message,
variant: "destructive",
});
console.error('Error signing up:', error);
throw error;
} finally {
setLoading(false);
}
};
const signOut = async () => {
try {
setLoading(true);
const { error } = await supabase.auth.signOut();
if (error) throw error;
toast({
title: "Signed out",
description: "You have been successfully signed out.",
});
} catch (error: any) {
toast({
title: "Error signing out",
description: error.message,
variant: "destructive",
});
console.error('Error signing out:', error);
} finally {
setLoading(false);
}
};
return (
<AuthContext.Provider
value={{
session,
user,
profile,
loading,
signIn,
signUp,
signOut,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@ -9,7 +9,33 @@ export type Json =
export type Database = { export type Database = {
public: { public: {
Tables: { Tables: {
[_ in never]: never profiles: {
Row: {
company: string | null
created_at: string
full_name: string | null
id: string
role: string | null
updated_at: string
}
Insert: {
company?: string | null
created_at?: string
full_name?: string | null
id: string
role?: string | null
updated_at?: string
}
Update: {
company?: string | null
created_at?: string
full_name?: string | null
id?: string
role?: string | null
updated_at?: string
}
Relationships: []
}
} }
Views: { Views: {
[_ in never]: never [_ in never]: never

194
src/pages/Auth.tsx Normal file
View File

@ -0,0 +1,194 @@
import { useState } from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import { ShieldCheck, Mail, Lock, User } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
const Auth = () => {
const { user, signIn, signUp, loading } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [fullName, setFullName] = useState('');
const [authError, setAuthError] = useState<string | null>(null);
// If user is already logged in, redirect to home page
if (user) {
return <Navigate to="/dashboard" />;
}
const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault();
setAuthError(null);
try {
await signIn(email, password);
} catch (error: any) {
setAuthError(error.message);
}
};
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault();
setAuthError(null);
try {
await signUp(email, password, fullName);
} catch (error: any) {
setAuthError(error.message);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-muted/30 px-4">
<div className="max-w-md w-full">
<div className="text-center mb-8">
<div className="inline-flex mb-6 p-4 bg-primary/10 rounded-full">
<ShieldCheck className="w-8 h-8 text-primary" />
</div>
<h1 className="text-3xl font-bold mb-2">SecuPolicy</h1>
<p className="text-muted-foreground">Sign in to access your account</p>
</div>
<Tabs defaultValue="login" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-6">
<TabsTrigger value="login">Login</TabsTrigger>
<TabsTrigger value="register">Register</TabsTrigger>
</TabsList>
<TabsContent value="login">
<Card>
<CardHeader>
<CardTitle>Welcome back</CardTitle>
<CardDescription>
Enter your credentials to access your account
</CardDescription>
</CardHeader>
<form onSubmit={handleSignIn}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder="name@example.com"
className="pl-10"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type="password"
className="pl-10"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
</div>
{authError && (
<div className="text-sm text-destructive">{authError}</div>
)}
</CardContent>
<CardFooter>
<Button
type="submit"
className="w-full rounded-full"
disabled={loading}
>
{loading ? 'Signing in...' : 'Sign In'}
</Button>
</CardFooter>
</form>
</Card>
</TabsContent>
<TabsContent value="register">
<Card>
<CardHeader>
<CardTitle>Create an account</CardTitle>
<CardDescription>
Enter your details to create your account
</CardDescription>
</CardHeader>
<form onSubmit={handleSignUp}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="full-name">Full Name</Label>
<div className="relative">
<User className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="full-name"
type="text"
placeholder="John Doe"
className="pl-10"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="register-email">Email</Label>
<div className="relative">
<Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="register-email"
type="email"
placeholder="name@example.com"
className="pl-10"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="register-password">Password</Label>
<div className="relative">
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="register-password"
type="password"
className="pl-10"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={6}
/>
</div>
</div>
{authError && (
<div className="text-sm text-destructive">{authError}</div>
)}
</CardContent>
<CardFooter>
<Button
type="submit"
className="w-full rounded-full"
disabled={loading}
>
{loading ? 'Creating account...' : 'Create Account'}
</Button>
</CardFooter>
</form>
</Card>
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default Auth;