Add user authentication and dynamic feature/category inputs

Implement Keycloak authentication, protected routes, and add combobox and autocomplete components for tool categories and features.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 776963d0-f75d-42e2-a57b-cc36bdff8495
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0b145113-c016-4f54-b000-13bd3b0ba8f0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0683fb79-a27c-485c-9333-5f4b288c4567/776963d0-f75d-42e2-a57b-cc36bdff8495/z4uWN6A
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
cheffe01
2026-05-25 13:20:15 +00:00
parent e03c51a75e
commit 7abb048edc
22 changed files with 1027 additions and 68 deletions
+70 -3
View File
@@ -1,8 +1,12 @@
import { Link, useLocation } from "wouter";
import { LayoutDashboard, Wrench, PlusCircle, BarChart3 } from "lucide-react";
import { LayoutDashboard, Wrench, PlusCircle, BarChart3, LogIn, LogOut, User } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
export function Layout({ children }: { children: React.ReactNode }) {
const [location] = useLocation();
const { user, isLoading, isAuthenticated, login, logout } = useAuth();
const links = [
{ href: "/", label: "Dashboard", icon: LayoutDashboard },
@@ -25,20 +29,83 @@ export function Layout({ children }: { children: React.ReactNode }) {
const isActive = location === link.href || (link.href !== "/" && location.startsWith(link.href));
const Icon = link.icon;
return (
<Link key={link.href} href={link.href} className={`flex items-center gap-3 px-3 py-2 rounded-md transition-colors ${isActive ? "bg-primary text-primary-foreground font-medium" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`}>
<Link
key={link.href}
href={link.href}
className={`flex items-center gap-3 px-3 py-2 rounded-md transition-colors ${isActive ? "bg-primary text-primary-foreground font-medium" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`}
>
<Icon className="w-5 h-5" />
{link.label}
</Link>
);
})}
</nav>
<div className="p-4 border-t">
{isLoading ? (
<div className="flex items-center gap-3 px-3 py-2">
<Skeleton className="w-8 h-8 rounded-full" />
<Skeleton className="h-4 w-24" />
</div>
) : isAuthenticated && user ? (
<div className="space-y-2">
<div className="flex items-center gap-3 px-3 py-2 rounded-md bg-muted/50">
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center shrink-0">
<User className="w-4 h-4 text-primary" />
</div>
<div className="min-w-0">
<p className="text-sm font-medium truncate text-foreground">
{user.name || user.preferredUsername || "User"}
</p>
{user.email && (
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
)}
</div>
</div>
<Button
variant="ghost"
size="sm"
className="w-full justify-start gap-2 text-muted-foreground hover:text-destructive"
onClick={logout}
data-testid="button-logout"
>
<LogOut className="w-4 h-4" />
Sign out
</Button>
</div>
) : (
<Button
variant="outline"
size="sm"
className="w-full gap-2"
onClick={() => login(location)}
data-testid="button-login"
>
<LogIn className="w-4 h-4" />
Sign in with Keycloak
</Button>
)}
</div>
</aside>
<main className="flex-1 flex flex-col min-w-0">
<header className="md:hidden border-b p-4 flex items-center bg-card">
<header className="md:hidden border-b p-4 flex items-center justify-between bg-card">
<div className="flex items-center gap-2 text-primary font-bold text-lg">
<Wrench className="w-5 h-5" />
<span>ToolRate</span>
</div>
{!isLoading && (
isAuthenticated ? (
<Button variant="ghost" size="sm" onClick={logout} data-testid="button-logout-mobile">
<LogOut className="w-4 h-4" />
</Button>
) : (
<Button variant="outline" size="sm" onClick={() => login(location)} data-testid="button-login-mobile">
<LogIn className="w-4 h-4 mr-1" />
Sign in
</Button>
)
)}
</header>
<div className="flex-1 p-6 md:p-8 overflow-auto">
{children}