22import { ref , computed , onMounted } from ' vue'
33import { useI18n } from ' vue-i18n'
44import { useRouter } from ' vue-router'
5- import { Loader2 , Info , Download } from ' lucide-vue-next'
5+ import { Loader2 , Info , Download , ChevronDown , PackagePlus } from ' lucide-vue-next'
66import { Button } from ' @/components/ui/button'
7- import { Input } from ' @/components/ui/input'
87import { McpCatalogService } from ' @/services/mcpCatalogService'
98
109// Props and emits
@@ -27,18 +26,38 @@ const servers = ref<any[]>([])
2726const searchTerm = ref (' ' )
2827const searchQuery = ref (' ' )
2928const selectedServerId = ref <string | null >(null )
29+ const selectedCategory = ref (' all' )
30+
31+ // Available categories (you can expand this based on your data)
32+ const categories = [
33+ { value: ' all' , label: ' All Categories' },
34+ { value: ' productivity' , label: ' Productivity' },
35+ { value: ' development' , label: ' Development' },
36+ { value: ' ai' , label: ' AI & Machine Learning' },
37+ { value: ' database' , label: ' Database' },
38+ { value: ' api' , label: ' API & Integration' }
39+ ]
3040
3141// Computed
3242const filteredServers = computed (() => {
3343 if (! searchQuery .value .trim ()) return []
3444
3545 const term = searchQuery .value .toLowerCase ()
36- return servers .value .filter (server =>
46+ let filtered = servers .value .filter (server =>
3747 server .name .toLowerCase ().includes (term ) ||
3848 server .description .toLowerCase ().includes (term ) ||
3949 server .author_name ?.toLowerCase ().includes (term ) ||
4050 server .category_name ?.toLowerCase ().includes (term )
4151 )
52+
53+ // Filter by category if not 'all'
54+ if (selectedCategory .value !== ' all' ) {
55+ filtered = filtered .filter (server =>
56+ server .category_name ?.toLowerCase () === selectedCategory .value .toLowerCase ()
57+ )
58+ }
59+
60+ return filtered
4261})
4362
4463
@@ -88,42 +107,69 @@ onMounted(() => {
88107 </script >
89108
90109<template >
91- <div class =" space-y-6" >
92- <!-- Step Header -->
93- <div >
94- <h2 class =" text-xl font-semibold text-gray-900 mb-2" >
95- {{ t('mcpInstallations.wizard.server.title') }}
96- </h2 >
97- <p class =" text-gray-600" >
98- {{ t('mcpInstallations.wizard.server.description') }}
99- </p >
100- </div >
101-
102- <!-- Search Input -->
103- <div class =" space-y-2" >
104- <label for =" server-search" class =" text-sm font-medium text-gray-700" >
105- {{ t('mcpInstallations.wizard.server.searchLabel') }}
106- </label >
107- <div class =" flex w-full items-center gap-1.5" >
108- <Input
109- id =" server-search"
110- v-model =" searchTerm"
111- type =" text"
112- :placeholder =" t('mcpInstallations.wizard.server.searchPlaceholder')"
113- @keyup.enter =" performSearch"
114- class =" flex-1"
115- />
116- <Button type =" button" @click =" performSearch" >
117- Search
118- </Button >
110+ <div class =" pt-10" >
111+ <div class =" mx-auto max-w-2xl" >
112+ <div class =" text-center" >
113+ <div class =" mx-auto size-16 text-gray-400" >
114+ <PackagePlus class =" w-full h-full" stroke-width =" 1.25" aria-hidden =" true" />
115+ </div >
116+ <h2 class =" mt-2 text-base font-semibold text-gray-900" >
117+ {{ t('mcpInstallations.wizard.server.title') }}
118+ </h2 >
119+ <p class =" mt-1 text-sm text-gray-500" >
120+ {{ t('mcpInstallations.wizard.server.description') }}
121+ </p >
119122 </div >
123+
124+ <!-- Search Form -->
125+ <form class =" mt-6 sm:flex sm:items-center" @submit.prevent =" performSearch" >
126+ <div class =" flex grow items-center rounded-md bg-white pl-3 outline-1 -outline-offset-1 outline-gray-300 has-[input:focus-within]:outline-2 has-[input:focus-within]:-outline-offset-2 has-[input:focus-within]:outline-primary" >
127+ <input
128+ v-model =" searchTerm"
129+ type =" text"
130+ name =" search"
131+ :aria-label =" t('mcpInstallations.wizard.server.searchLabel')"
132+ class =" block min-w-0 grow py-1.5 pr-3 text-base text-gray-900 placeholder:text-gray-400 focus:outline-none sm:text-sm/6"
133+ :placeholder =" t('mcpInstallations.wizard.server.searchPlaceholder')"
134+ @keyup.enter =" performSearch"
135+ />
136+ <div class =" grid shrink-0 grid-cols-1 focus-within:relative" >
137+ <select
138+ v-model =" selectedCategory"
139+ name =" category"
140+ :aria-label =" t('mcpInstallations.wizard.server.categoryLabel')"
141+ class =" col-start-1 row-start-1 w-full appearance-none rounded-md py-1.5 pr-7 pl-3 text-base text-gray-500 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-primary sm:text-sm/6"
142+ >
143+ <option
144+ v-for =" category in categories"
145+ :key =" category.value"
146+ :value =" category.value"
147+ >
148+ {{ category.label }}
149+ </option >
150+ </select >
151+ <ChevronDown class =" pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4" aria-hidden =" true" />
152+ </div >
153+ </div >
154+ <div class =" mt-3 sm:mt-0 sm:ml-4 sm:shrink-0" >
155+ <button
156+ type =" submit"
157+ class =" block w-full rounded-md bg-primary px-3 py-2 text-center text-sm font-semibold text-primary-foreground shadow-xs hover:bg-primary/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
158+ :disabled =" isLoading"
159+ >
160+ {{ isLoading ? t('mcpInstallations.wizard.server.searching') : t('mcpInstallations.wizard.server.searchButton') }}
161+ </button >
162+ </div >
163+ </form >
120164 </div >
121165
122166 <!-- Error Alert -->
123- <div v-if =" error" class =" rounded-md bg-red-50 p-4" >
167+ <div v-if =" error" class =" mt-14 rounded-md bg-red-50 p-4" >
124168 <div class =" flex" >
125169 <div class =" ml-3" >
126- <h3 class =" text-sm font-medium text-red-800" >Error loading servers</h3 >
170+ <h3 class =" text-sm font-medium text-red-800" >
171+ {{ t('mcpInstallations.wizard.server.errorTitle') }}
172+ </h3 >
127173 <div class =" mt-2 text-sm text-red-700" >
128174 <p >{{ error }}</p >
129175 </div >
@@ -143,13 +189,13 @@ onMounted(() => {
143189 </div >
144190
145191 <!-- Loading State -->
146- <div v-if =" isLoading" class =" flex items-center justify-center py-8" >
192+ <div v-if =" isLoading" class =" mt-14 flex items-center justify-center py-8" >
147193 <Loader2 class =" h-6 w-6 animate-spin mr-2 text-gray-400" />
148194 <span class =" text-gray-600" >{{ t('messages.loading') }}</span >
149195 </div >
150196
151197 <!-- Server List (only show when there's a search query) -->
152- <div v-else-if =" searchQuery.trim() && filteredServers.length > 0" class =" space-y-4" >
198+ <div v-else-if =" searchQuery.trim() && filteredServers.length > 0" class =" mt-14 space-y-4" >
153199 <div
154200 v-for =" server in filteredServers"
155201 :key =" server.id"
@@ -205,13 +251,13 @@ onMounted(() => {
205251 </div >
206252
207253 <!-- No Results -->
208- <div v-else-if =" searchQuery.trim() && filteredServers.length === 0" class =" text-center py-8" >
254+ <div v-else-if =" searchQuery.trim() && filteredServers.length === 0" class =" mt-14 text-center py-8" >
209255 <p class =" text-gray-500" >{{ t('mcpInstallations.wizard.server.noServersFound') }}</p >
210256 </div >
211257
212258 <!-- Empty State (when no search performed) -->
213- <div v-else-if =" !searchQuery.trim() && !isLoading" class =" text-center py-8" >
214- <p class =" text-gray-500" >Enter a search term and click Search to find MCP servers... </p >
259+ <div v-else-if =" !searchQuery.trim() && !isLoading" class =" mt-14 text-center py-8" >
260+ <p class =" text-gray-500" >{{ t('mcpInstallations.wizard.server.emptyStateMessage') }} </p >
215261 </div >
216262 </div >
217263</template >
0 commit comments