feat(page): Replace Select components with SearchableSelect for improved usability

This commit is contained in:
Abhimanyu Saharan
2026-02-05 00:44:23 +05:30
parent 5135887c3f
commit 2a557af22d
2 changed files with 82 additions and 70 deletions

View File

@@ -9,19 +9,15 @@ import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { DashboardShell } from "@/components/templates/DashboardShell"; import { DashboardShell } from "@/components/templates/DashboardShell";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import SearchableSelect, {
type SearchableSelectOption,
} from "@/components/ui/searchable-select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { getApiBaseUrl } from "@/lib/api-base"; import { getApiBaseUrl } from "@/lib/api-base";
import { import {
DEFAULT_IDENTITY_TEMPLATE, DEFAULT_IDENTITY_TEMPLATE,
DEFAULT_SOUL_TEMPLATE, DEFAULT_SOUL_TEMPLATE,
} from "@/lib/agent-templates"; } from "@/lib/agent-templates";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const apiBase = getApiBaseUrl(); const apiBase = getApiBaseUrl();
@@ -43,6 +39,17 @@ type Board = {
slug: string; slug: string;
}; };
const HEARTBEAT_TARGET_OPTIONS: SearchableSelectOption[] = [
{ value: "none", label: "None (no outbound message)" },
{ value: "last", label: "Last channel" },
];
const getBoardOptions = (boards: Board[]): SearchableSelectOption[] =>
boards.map((board) => ({
value: board.id,
label: board.name,
}));
export default function EditAgentPage() { export default function EditAgentPage() {
const { getToken, isSignedIn } = useAuth(); const { getToken, isSignedIn } = useAuth();
const router = useRouter(); const router = useRouter();
@@ -231,22 +238,19 @@ export default function EditAgentPage() {
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Board <span className="text-red-500">*</span> Board <span className="text-red-500">*</span>
</label> </label>
<Select <SearchableSelect
ariaLabel="Select board"
value={boardId} value={boardId}
onValueChange={(value) => setBoardId(value)} onValueChange={setBoardId}
options={getBoardOptions(boards)}
placeholder="Select board"
searchPlaceholder="Search boards..."
emptyMessage="No matching boards."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={boards.length === 0} disabled={boards.length === 0}
> />
<SelectTrigger>
<SelectValue placeholder="Select board" />
</SelectTrigger>
<SelectContent>
{boards.map((board) => (
<SelectItem key={board.id} value={board.id}>
{board.name}
</SelectItem>
))}
</SelectContent>
</Select>
{boards.length === 0 ? ( {boards.length === 0 ? (
<p className="text-xs text-slate-500"> <p className="text-xs text-slate-500">
Create a board before assigning agents. Create a board before assigning agents.
@@ -260,7 +264,7 @@ export default function EditAgentPage() {
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500"> <p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Agent persona Agent persona
</p> </p>
<div className="mt-4 space-y-4"> <div className="mt-4 grid gap-6 md:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Identity template Identity template
@@ -271,6 +275,10 @@ export default function EditAgentPage() {
rows={8} rows={8}
disabled={isLoading} disabled={isLoading}
/> />
<p className="text-xs text-slate-500">
Keep the agent_name and agent_id variables unchanged so
the gateway can render them correctly.
</p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
@@ -309,21 +317,19 @@ export default function EditAgentPage() {
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Target Target
</label> </label>
<Select <SearchableSelect
ariaLabel="Select heartbeat target"
value={heartbeatTarget} value={heartbeatTarget}
onValueChange={(value) => setHeartbeatTarget(value)} onValueChange={setHeartbeatTarget}
options={HEARTBEAT_TARGET_OPTIONS}
placeholder="Select target"
searchPlaceholder="Search targets..."
emptyMessage="No matching targets."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={isLoading} disabled={isLoading}
> />
<SelectTrigger>
<SelectValue placeholder="Select target" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
None (no outbound message)
</SelectItem>
<SelectItem value="last">Last channel</SelectItem>
</SelectContent>
</Select>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -9,19 +9,15 @@ import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { DashboardShell } from "@/components/templates/DashboardShell"; import { DashboardShell } from "@/components/templates/DashboardShell";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import SearchableSelect, {
type SearchableSelectOption,
} from "@/components/ui/searchable-select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { getApiBaseUrl } from "@/lib/api-base"; import { getApiBaseUrl } from "@/lib/api-base";
import { import {
DEFAULT_IDENTITY_TEMPLATE, DEFAULT_IDENTITY_TEMPLATE,
DEFAULT_SOUL_TEMPLATE, DEFAULT_SOUL_TEMPLATE,
} from "@/lib/agent-templates"; } from "@/lib/agent-templates";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const apiBase = getApiBaseUrl(); const apiBase = getApiBaseUrl();
@@ -36,6 +32,17 @@ type Board = {
slug: string; slug: string;
}; };
const HEARTBEAT_TARGET_OPTIONS: SearchableSelectOption[] = [
{ value: "none", label: "None (no outbound message)" },
{ value: "last", label: "Last channel" },
];
const getBoardOptions = (boards: Board[]): SearchableSelectOption[] =>
boards.map((board) => ({
value: board.id,
label: board.name,
}));
export default function NewAgentPage() { export default function NewAgentPage() {
const router = useRouter(); const router = useRouter();
const { getToken, isSignedIn } = useAuth(); const { getToken, isSignedIn } = useAuth();
@@ -177,22 +184,19 @@ export default function NewAgentPage() {
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Board <span className="text-red-500">*</span> Board <span className="text-red-500">*</span>
</label> </label>
<Select <SearchableSelect
ariaLabel="Select board"
value={boardId} value={boardId}
onValueChange={(value) => setBoardId(value)} onValueChange={setBoardId}
options={getBoardOptions(boards)}
placeholder="Select board"
searchPlaceholder="Search boards..."
emptyMessage="No matching boards."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={boards.length === 0} disabled={boards.length === 0}
> />
<SelectTrigger>
<SelectValue placeholder="Select board" />
</SelectTrigger>
<SelectContent>
{boards.map((board) => (
<SelectItem key={board.id} value={board.id}>
{board.name}
</SelectItem>
))}
</SelectContent>
</Select>
{boards.length === 0 ? ( {boards.length === 0 ? (
<p className="text-xs text-slate-500"> <p className="text-xs text-slate-500">
Create a board before adding agents. Create a board before adding agents.
@@ -206,7 +210,7 @@ export default function NewAgentPage() {
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500"> <p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Agent persona Agent persona
</p> </p>
<div className="mt-4 space-y-4"> <div className="mt-4 grid gap-6 md:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Identity template Identity template
@@ -217,6 +221,10 @@ export default function NewAgentPage() {
rows={8} rows={8}
disabled={isLoading} disabled={isLoading}
/> />
<p className="text-xs text-slate-500">
Keep the agent_name and agent_id variables unchanged so
the gateway can render them correctly.
</p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
@@ -255,21 +263,19 @@ export default function NewAgentPage() {
<label className="text-sm font-medium text-slate-900"> <label className="text-sm font-medium text-slate-900">
Target Target
</label> </label>
<Select <SearchableSelect
ariaLabel="Select heartbeat target"
value={heartbeatTarget} value={heartbeatTarget}
onValueChange={(value) => setHeartbeatTarget(value)} onValueChange={setHeartbeatTarget}
options={HEARTBEAT_TARGET_OPTIONS}
placeholder="Select target"
searchPlaceholder="Search targets..."
emptyMessage="No matching targets."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
disabled={isLoading} disabled={isLoading}
> />
<SelectTrigger>
<SelectValue placeholder="Select target" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
None (no outbound message)
</SelectItem>
<SelectItem value="last">Last channel</SelectItem>
</SelectContent>
</Select>
</div> </div>
</div> </div>
</div> </div>