Implement profile page and navigation changes
- Implemented a profile page with necessary elements for the micro-saas purpose. - Modified the dashboard to hide the navigation bar after login.
This commit is contained in:
parent
2f66c2ddcd
commit
2f66cd3f11
16
src/App.tsx
16
src/App.tsx
@ -9,6 +9,7 @@ import Index from "./pages/Index";
|
|||||||
import Auth from "./pages/Auth";
|
import Auth from "./pages/Auth";
|
||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
import Dashboard from "./pages/Dashboard";
|
import Dashboard from "./pages/Dashboard";
|
||||||
|
import Profile from "./pages/Profile";
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
@ -48,13 +49,16 @@ const AuthGuard = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
// App needs to be wrapped with BrowserRouter to use the AuthProvider with routing capabilities
|
// App needs to be wrapped with BrowserRouter to use the AuthProvider with routing capabilities
|
||||||
const AppContent = () => {
|
const AppContent = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const isDashboard = location.pathname === '/dashboard';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Sonner />
|
<Sonner />
|
||||||
<Header />
|
{!isDashboard && <Header />}
|
||||||
<div className="pt-16 sm:pt-20">
|
<div className={isDashboard ? "" : "pt-16 sm:pt-20"}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Index />} />
|
<Route path="/" element={<Index />} />
|
||||||
<Route
|
<Route
|
||||||
@ -73,6 +77,14 @@ const AppContent = () => {
|
|||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/profile"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Profile />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
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, User, LogOut } from 'lucide-react';
|
import { ShieldCheck, Menu, X, User, LogOut, UserCircle } from 'lucide-react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -84,7 +84,10 @@ const Header = () => {
|
|||||||
<Link to="/dashboard">Dashboard</Link>
|
<Link to="/dashboard">Dashboard</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link to="/profile">Profile</Link>
|
<Link to="/profile">
|
||||||
|
<UserCircle className="mr-2 h-4 w-4" />
|
||||||
|
<span>Profile</span>
|
||||||
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={() => signOut()} className="text-destructive">
|
<DropdownMenuItem onClick={() => signOut()} className="text-destructive">
|
||||||
@ -129,7 +132,8 @@ const Header = () => {
|
|||||||
</a>
|
</a>
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Link to="/profile" className="block py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
<Link to="/profile" className="flex items-center py-2 text-base font-medium" onClick={() => setMobileMenuOpen(false)}>
|
||||||
|
<UserCircle className="mr-2 h-5 w-5" />
|
||||||
Profile
|
Profile
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
|
@ -43,7 +43,7 @@ const TableFooter = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<tfoot
|
<tfoot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("bg-primary font-medium text-primary-foreground", className)}
|
className={cn("bg-primary-50 font-medium text-primary-900", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
241
src/pages/Profile.tsx
Normal file
241
src/pages/Profile.tsx
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
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 { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||||
|
import { User, Mail, Building, Briefcase, Lock, Save } from 'lucide-react';
|
||||||
|
|
||||||
|
const Profile = () => {
|
||||||
|
const { user, profile, signOut } = useAuth();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
fullName: profile?.full_name || '',
|
||||||
|
email: user?.email || '',
|
||||||
|
company: profile?.company || '',
|
||||||
|
role: profile?.role || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "You must be logged in to update your profile",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.update({
|
||||||
|
full_name: formData.fullName,
|
||||||
|
company: formData.company,
|
||||||
|
role: formData.role,
|
||||||
|
})
|
||||||
|
.eq('id', user.id);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Profile updated",
|
||||||
|
description: "Your profile has been successfully updated.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error updating profile:', error);
|
||||||
|
toast({
|
||||||
|
title: "Error updating profile",
|
||||||
|
description: error.message || "An error occurred while updating your profile",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitials = () => {
|
||||||
|
if (formData.fullName) {
|
||||||
|
return formData.fullName
|
||||||
|
.split(' ')
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
return user?.email?.substring(0, 2).toUpperCase() || 'U';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8 text-center">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Profile not available</h1>
|
||||||
|
<p className="mb-4">You need to be logged in to view your profile.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">My Profile</h1>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<Avatar className="h-24 w-24">
|
||||||
|
<AvatarFallback className="text-2xl">{getInitials()}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-xl">{profile?.full_name || user.email}</CardTitle>
|
||||||
|
<CardDescription>{profile?.role || 'SecuPolicy User'}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Mail className="h-5 w-5 text-muted-foreground" />
|
||||||
|
<span className="text-sm">{user.email}</span>
|
||||||
|
</div>
|
||||||
|
{profile?.company && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Building className="h-5 w-5 text-muted-foreground" />
|
||||||
|
<span className="text-sm">{profile.company}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{profile?.role && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||||
|
<span className="text-sm">{profile.role}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="flex flex-col gap-2">
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => signOut()}
|
||||||
|
>
|
||||||
|
<Lock className="mr-2 h-4 w-4" />
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Edit Profile</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Update your personal information and company details
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="fullName">Full Name</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<User className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="fullName"
|
||||||
|
name="fullName"
|
||||||
|
placeholder="Your full name"
|
||||||
|
value={formData.fullName}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<Mail className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value={formData.email}
|
||||||
|
disabled
|
||||||
|
className="pl-10 bg-muted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Email address cannot be changed
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="company">Company</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<Building className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="company"
|
||||||
|
name="company"
|
||||||
|
placeholder="Company name"
|
||||||
|
value={formData.company}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="role">Role</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<Briefcase className="h-4 w-4 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
id="role"
|
||||||
|
name="role"
|
||||||
|
placeholder="Your role at the company"
|
||||||
|
value={formData.role}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="ml-auto"
|
||||||
|
>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
{isLoading ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Profile;
|
Loading…
x
Reference in New Issue
Block a user