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:
@@ -0,0 +1,74 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useListAllFeatures } from "@workspace/api-client-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface FeatureInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
"data-testid"?: string;
|
||||
}
|
||||
|
||||
export function FeatureInput({ value, onChange, placeholder, "data-testid": testId }: FeatureInputProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const allFeatures = useListAllFeatures();
|
||||
|
||||
const known: string[] = allFeatures.data ?? [];
|
||||
|
||||
const suggestions = value.trim().length >= 1
|
||||
? known.filter(
|
||||
(f) =>
|
||||
f.toLowerCase().includes(value.toLowerCase()) &&
|
||||
f.toLowerCase() !== value.toLowerCase()
|
||||
).slice(0, 6)
|
||||
: [];
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(e: MouseEvent) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative w-full">
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setOpen(true);
|
||||
}}
|
||||
onFocus={() => setOpen(true)}
|
||||
placeholder={placeholder}
|
||||
data-testid={testId}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{open && suggestions.length > 0 && (
|
||||
<div className="absolute z-50 top-full mt-1 w-full rounded-md border bg-popover shadow-md text-sm overflow-hidden">
|
||||
{suggestions.map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
type="button"
|
||||
className={cn(
|
||||
"w-full text-left px-3 py-2 hover:bg-muted transition-colors text-foreground",
|
||||
)}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
onChange(s);
|
||||
setOpen(false);
|
||||
}}
|
||||
data-testid={`suggestion-feature-${s}`}
|
||||
>
|
||||
{s}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user