EdgeFunctionSearchResults.tsx136 lines · main
1'use client'
2
3import { useParams } from 'common'
4import { EdgeFunctions } from 'icons'
5import { Loader2 } from 'lucide-react'
6import { useMemo } from 'react'
7
8import {
9 EmptyState,
10 ResultsList,
11 SkeletonResults,
12 type SearchResult,
13} from './ContextSearchResults.shared'
14import { useEdgeFunctionsQuery } from '@/data/edge-functions/edge-functions-query'
15
16interface EdgeFunctionSearchResultsProps {
17 query: string
18}
19
20export function EdgeFunctionSearchResults({ query }: EdgeFunctionSearchResultsProps) {
21 const { ref: projectRef } = useParams()
22
23 const trimmedQuery = query.trim()
24
25 const {
26 data: functions,
27 isLoading: isLoadingFunctions,
28 isError: isErrorFunctions,
29 } = useEdgeFunctionsQuery(
30 {
31 projectRef,
32 },
33 {
34 enabled: !!projectRef,
35 }
36 )
37
38 const functionResults: SearchResult[] = useMemo(() => {
39 if (!functions) return []
40
41 const filtered = trimmedQuery
42 ? functions.filter((func) => {
43 const searchLower = trimmedQuery.toLowerCase()
44 const functionName = func.name?.toLowerCase() || ''
45 const functionSlug = func.slug?.toLowerCase() || ''
46
47 return functionName.includes(searchLower) || functionSlug.includes(searchLower)
48 })
49 : functions
50
51 // Limit results for performance
52 return filtered.slice(0, 20).map((func) => {
53 const displayName = func.name || func.slug || 'Untitled Function'
54 const description = func.version ? `Version ${func.version}` : undefined
55
56 return {
57 id: String(func.id),
58 name: displayName,
59 description,
60 }
61 })
62 }, [functions, trimmedQuery])
63
64 const totalFunctions = functions?.length ?? 0
65
66 const renderFooter = () => (
67 <div className="absolute bottom-0 left-0 right-0 flex items-center justify-between min-h-9 h-9 px-4 border-t bg-surface-200 text-xs text-foreground-light z-10">
68 <div className="flex items-center gap-x-2">
69 {isLoadingFunctions ? (
70 <span className="flex items-center gap-2">
71 <Loader2 size={14} className="animate-spin" /> Loading...
72 </span>
73 ) : (
74 <span>
75 Total: {totalFunctions.toLocaleString()} function{totalFunctions !== 1 ? 's' : ''}
76 </span>
77 )}
78 </div>
79 </div>
80 )
81
82 if (isLoadingFunctions) {
83 return (
84 <div className="relative h-full flex flex-col">
85 <div className="flex-1 min-h-0 overflow-hidden">
86 <SkeletonResults />
87 </div>
88 {renderFooter()}
89 </div>
90 )
91 }
92
93 if (isErrorFunctions) {
94 return (
95 <div className="relative h-full flex flex-col">
96 <div className="flex-1 min-h-0 overflow-hidden">
97 <div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
98 <EdgeFunctions className="h-6 w-6" strokeWidth={1.5} />
99 <p className="text-sm">Failed to load edge functions</p>
100 </div>
101 </div>
102 {renderFooter()}
103 </div>
104 )
105 }
106
107 if (functionResults.length === 0) {
108 return (
109 <div className="relative h-full flex flex-col">
110 <div className="flex-1 min-h-0 overflow-hidden">
111 <EmptyState icon={EdgeFunctions} label="Edge Functions" query={query} />
112 </div>
113 {renderFooter()}
114 </div>
115 )
116 }
117
118 return (
119 <div className="relative h-full flex flex-col">
120 <div className="flex-1 min-h-0 overflow-hidden">
121 <ResultsList
122 results={functionResults}
123 icon={EdgeFunctions}
124 getRoute={(result) => {
125 const func = functions?.find((f) => String(f.id) === result.id)
126 if (!func || !projectRef) return `/project/${projectRef}/functions` as `/${string}`
127
128 return `/project/${projectRef}/functions/${func.slug}` as `/${string}`
129 }}
130 className="pb-9"
131 />
132 </div>
133 {renderFooter()}
134 </div>
135 )
136}